mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-10 05:33:40 +01:00
Merge pull request #2534 from tnull/2023-08-upstream-preflight-probing
Upstream and fix preflight probing
This commit is contained in:
commit
36af1f06fa
10 changed files with 413 additions and 80 deletions
|
@ -371,6 +371,7 @@ fn send_payment(source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, p
|
||||||
channel_features: dest.channel_features(),
|
channel_features: dest.channel_features(),
|
||||||
fee_msat: amt,
|
fee_msat: amt,
|
||||||
cltv_expiry_delta: 200,
|
cltv_expiry_delta: 200,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None }],
|
}], blinded_tail: None }],
|
||||||
route_params: None,
|
route_params: None,
|
||||||
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
|
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
|
||||||
|
@ -405,6 +406,7 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
|
||||||
channel_features: middle.channel_features(),
|
channel_features: middle.channel_features(),
|
||||||
fee_msat: first_hop_fee,
|
fee_msat: first_hop_fee,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}, RouteHop {
|
}, RouteHop {
|
||||||
pubkey: dest.get_our_node_id(),
|
pubkey: dest.get_our_node_id(),
|
||||||
node_features: dest.node_features(),
|
node_features: dest.node_features(),
|
||||||
|
@ -412,6 +414,7 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
|
||||||
channel_features: dest.channel_features(),
|
channel_features: dest.channel_features(),
|
||||||
fee_msat: amt,
|
fee_msat: amt,
|
||||||
cltv_expiry_delta: 200,
|
cltv_expiry_delta: 200,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None }],
|
}], blinded_tail: None }],
|
||||||
route_params: None,
|
route_params: None,
|
||||||
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
|
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
|
||||||
|
|
|
@ -1683,6 +1683,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA as u32,
|
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA as u32,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None };
|
}], blinded_tail: None };
|
||||||
|
|
||||||
$nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
|
$nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
// You may not use this file except in accordance with one or both of these
|
// You may not use this file except in accordance with one or both of these
|
||||||
// licenses.
|
// 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 bitcoin_hashes::Hash;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ use lightning::chain;
|
||||||
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
|
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
|
||||||
use lightning::sign::{NodeSigner, SignerProvider, EntropySource};
|
use lightning::sign::{NodeSigner, SignerProvider, EntropySource};
|
||||||
use lightning::ln::PaymentHash;
|
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::routing::router::{PaymentParameters, RouteParameters, Router};
|
||||||
use lightning::util::logger::Logger;
|
use lightning::util::logger::Logger;
|
||||||
|
|
||||||
|
@ -32,22 +32,12 @@ use core::time::Duration;
|
||||||
/// with the same [`PaymentHash`] is never sent.
|
/// with the same [`PaymentHash`] is never sent.
|
||||||
///
|
///
|
||||||
/// If you wish to use a different payment idempotency token, see [`pay_invoice_with_id`].
|
/// 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>(
|
pub fn pay_invoice<C: AChannelManager>(
|
||||||
invoice: &Bolt11Invoice, retry_strategy: Retry,
|
invoice: &Bolt11Invoice, retry_strategy: Retry, channelmanager: &C
|
||||||
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
|
|
||||||
) -> Result<PaymentId, PaymentError>
|
) -> 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());
|
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)
|
.map(|()| payment_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,22 +51,12 @@ where
|
||||||
/// [`PaymentHash`] has never been paid before.
|
/// [`PaymentHash`] has never been paid before.
|
||||||
///
|
///
|
||||||
/// See [`pay_invoice`] for a variant which uses the [`PaymentHash`] for the idempotency token.
|
/// 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>(
|
pub fn pay_invoice_with_id<C: AChannelManager>(
|
||||||
invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry,
|
invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry, channelmanager: &C
|
||||||
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
|
|
||||||
) -> Result<(), PaymentError>
|
) -> 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"))?;
|
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
|
/// Pays the given zero-value [`Bolt11Invoice`] using the given amount, retrying if needed based on
|
||||||
|
@ -88,19 +68,9 @@ where
|
||||||
///
|
///
|
||||||
/// If you wish to use a different payment idempotency token, see
|
/// If you wish to use a different payment idempotency token, see
|
||||||
/// [`pay_zero_value_invoice_with_id`].
|
/// [`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>(
|
pub fn pay_zero_value_invoice<C: AChannelManager>(
|
||||||
invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry,
|
invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry, channelmanager: &C
|
||||||
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
|
|
||||||
) -> Result<PaymentId, PaymentError>
|
) -> 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());
|
let payment_id = PaymentId(invoice.payment_hash().into_inner());
|
||||||
pay_zero_value_invoice_with_id(invoice, amount_msats, payment_id, retry_strategy,
|
pay_zero_value_invoice_with_id(invoice, amount_msats, payment_id, retry_strategy,
|
||||||
|
@ -119,25 +89,16 @@ where
|
||||||
///
|
///
|
||||||
/// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the
|
/// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the
|
||||||
/// idempotency token.
|
/// 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,
|
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>
|
) -> 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() {
|
if invoice.amount_milli_satoshis().is_some() {
|
||||||
Err(PaymentError::Invoice("amount unexpected"))
|
Err(PaymentError::Invoice("amount unexpected"))
|
||||||
} else {
|
} else {
|
||||||
pay_invoice_using_amount(invoice, amount_msats, payment_id, retry_strategy,
|
pay_invoice_using_amount(invoice, amount_msats, payment_id, retry_strategy,
|
||||||
channelmanager)
|
channelmanager.get_cm())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +124,66 @@ fn pay_invoice_using_amount<P: Deref>(
|
||||||
payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy)
|
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 {
|
fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration {
|
||||||
invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time()
|
invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time()
|
||||||
}
|
}
|
||||||
|
@ -176,6 +197,15 @@ pub enum PaymentError {
|
||||||
Sending(RetryableSendFailure),
|
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.
|
/// A trait defining behavior of a [`Bolt11Invoice`] payer.
|
||||||
///
|
///
|
||||||
/// Useful for unit testing internal methods.
|
/// Useful for unit testing internal methods.
|
||||||
|
|
|
@ -77,7 +77,7 @@ use core::time::Duration;
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
|
|
||||||
// Re-export this for use in the public API.
|
// Re-export this for use in the public API.
|
||||||
pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
|
pub use crate::ln::outbound_payment::{PaymentSendFailure, ProbeSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
|
||||||
use crate::ln::script::ShutdownScript;
|
use crate::ln::script::ShutdownScript;
|
||||||
|
|
||||||
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
|
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
|
||||||
|
@ -839,33 +839,46 @@ pub type SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L> =
|
||||||
&'g L
|
&'g L
|
||||||
>;
|
>;
|
||||||
|
|
||||||
macro_rules! define_test_pub_trait { ($vis: vis) => {
|
/// A trivial trait which describes any [`ChannelManager`].
|
||||||
/// A trivial trait which describes any [`ChannelManager`] used in testing.
|
pub trait AChannelManager {
|
||||||
$vis trait AChannelManager {
|
/// A type implementing [`chain::Watch`].
|
||||||
type Watch: chain::Watch<Self::Signer> + ?Sized;
|
type Watch: chain::Watch<Self::Signer> + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::Watch`].
|
||||||
type M: Deref<Target = Self::Watch>;
|
type M: Deref<Target = Self::Watch>;
|
||||||
|
/// A type implementing [`BroadcasterInterface`].
|
||||||
type Broadcaster: BroadcasterInterface + ?Sized;
|
type Broadcaster: BroadcasterInterface + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::Broadcaster`].
|
||||||
type T: Deref<Target = Self::Broadcaster>;
|
type T: Deref<Target = Self::Broadcaster>;
|
||||||
|
/// A type implementing [`EntropySource`].
|
||||||
type EntropySource: EntropySource + ?Sized;
|
type EntropySource: EntropySource + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::EntropySource`].
|
||||||
type ES: Deref<Target = Self::EntropySource>;
|
type ES: Deref<Target = Self::EntropySource>;
|
||||||
|
/// A type implementing [`NodeSigner`].
|
||||||
type NodeSigner: NodeSigner + ?Sized;
|
type NodeSigner: NodeSigner + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::NodeSigner`].
|
||||||
type NS: Deref<Target = Self::NodeSigner>;
|
type NS: Deref<Target = Self::NodeSigner>;
|
||||||
|
/// A type implementing [`WriteableEcdsaChannelSigner`].
|
||||||
type Signer: WriteableEcdsaChannelSigner + Sized;
|
type Signer: WriteableEcdsaChannelSigner + Sized;
|
||||||
|
/// A type implementing [`SignerProvider`] for [`Self::Signer`].
|
||||||
type SignerProvider: SignerProvider<Signer = Self::Signer> + ?Sized;
|
type SignerProvider: SignerProvider<Signer = Self::Signer> + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::SignerProvider`].
|
||||||
type SP: Deref<Target = Self::SignerProvider>;
|
type SP: Deref<Target = Self::SignerProvider>;
|
||||||
|
/// A type implementing [`FeeEstimator`].
|
||||||
type FeeEstimator: FeeEstimator + ?Sized;
|
type FeeEstimator: FeeEstimator + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::FeeEstimator`].
|
||||||
type F: Deref<Target = Self::FeeEstimator>;
|
type F: Deref<Target = Self::FeeEstimator>;
|
||||||
|
/// A type implementing [`Router`].
|
||||||
type Router: Router + ?Sized;
|
type Router: Router + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::Router`].
|
||||||
type R: Deref<Target = Self::Router>;
|
type R: Deref<Target = Self::Router>;
|
||||||
|
/// A type implementing [`Logger`].
|
||||||
type Logger: Logger + ?Sized;
|
type Logger: Logger + ?Sized;
|
||||||
|
/// A type that may be dereferenced to [`Self::Logger`].
|
||||||
type L: Deref<Target = Self::Logger>;
|
type L: Deref<Target = Self::Logger>;
|
||||||
|
/// Returns a reference to the actual [`ChannelManager`] object.
|
||||||
fn get_cm(&self) -> &ChannelManager<Self::M, Self::T, Self::ES, Self::NS, Self::SP, Self::F, Self::R, Self::L>;
|
fn get_cm(&self) -> &ChannelManager<Self::M, Self::T, Self::ES, Self::NS, Self::SP, Self::F, Self::R, Self::L>;
|
||||||
}
|
}
|
||||||
} }
|
|
||||||
#[cfg(any(test, feature = "_test_utils"))]
|
|
||||||
define_test_pub_trait!(pub);
|
|
||||||
#[cfg(not(any(test, feature = "_test_utils")))]
|
|
||||||
define_test_pub_trait!(pub(crate));
|
|
||||||
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> AChannelManager
|
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> AChannelManager
|
||||||
for ChannelManager<M, T, ES, NS, SP, F, R, L>
|
for ChannelManager<M, T, ES, NS, SP, F, R, L>
|
||||||
where
|
where
|
||||||
|
@ -3546,6 +3559,116 @@ where
|
||||||
outbound_payment::payment_is_probe(payment_hash, payment_id, self.probing_cookie_secret)
|
outbound_payment::payment_is_probe(payment_hash, payment_id, self.probing_cookie_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends payment probes over all paths of a route that would be used to pay the given
|
||||||
|
/// amount to the given `node_id`.
|
||||||
|
///
|
||||||
|
/// See [`ChannelManager::send_preflight_probes`] for more information.
|
||||||
|
pub fn send_spontaneous_preflight_probes(
|
||||||
|
&self, node_id: PublicKey, amount_msat: u64, final_cltv_expiry_delta: u32,
|
||||||
|
liquidity_limit_multiplier: Option<u64>,
|
||||||
|
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbeSendFailure> {
|
||||||
|
let payment_params =
|
||||||
|
PaymentParameters::from_node_id(node_id, final_cltv_expiry_delta);
|
||||||
|
|
||||||
|
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
|
||||||
|
|
||||||
|
self.send_preflight_probes(route_params, liquidity_limit_multiplier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends payment probes over all paths of a route that would be used to pay a route found
|
||||||
|
/// according to the given [`RouteParameters`].
|
||||||
|
///
|
||||||
|
/// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting
|
||||||
|
/// the actual payment. Note this is only useful if there likely is sufficient time for the
|
||||||
|
/// probe to settle before sending out the actual payment, e.g., when waiting for user
|
||||||
|
/// confirmation in a wallet UI.
|
||||||
|
///
|
||||||
|
/// Otherwise, there is a chance the probe could take up some liquidity needed to complete the
|
||||||
|
/// actual payment. Users should therefore be cautious and might avoid sending probes if
|
||||||
|
/// liquidity is scarce and/or they don't expect the probe to return before they send the
|
||||||
|
/// payment. To mitigate this issue, channels with available liquidity less than the required
|
||||||
|
/// amount times the given `liquidity_limit_multiplier` won't be used to send pre-flight
|
||||||
|
/// probes. If `None` is given as `liquidity_limit_multiplier`, it defaults to `3`.
|
||||||
|
pub fn send_preflight_probes(
|
||||||
|
&self, route_params: RouteParameters, liquidity_limit_multiplier: Option<u64>,
|
||||||
|
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbeSendFailure> {
|
||||||
|
let liquidity_limit_multiplier = liquidity_limit_multiplier.unwrap_or(3);
|
||||||
|
|
||||||
|
let payer = self.get_our_node_id();
|
||||||
|
let usable_channels = self.list_usable_channels();
|
||||||
|
let first_hops = usable_channels.iter().collect::<Vec<_>>();
|
||||||
|
let inflight_htlcs = self.compute_inflight_htlcs();
|
||||||
|
|
||||||
|
let route = self
|
||||||
|
.router
|
||||||
|
.find_route(&payer, &route_params, Some(&first_hops), inflight_htlcs)
|
||||||
|
.map_err(|e| {
|
||||||
|
log_error!(self.logger, "Failed to find path for payment probe: {:?}", e);
|
||||||
|
ProbeSendFailure::RouteNotFound
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut used_liquidity_map = HashMap::with_capacity(first_hops.len());
|
||||||
|
|
||||||
|
let mut res = Vec::new();
|
||||||
|
|
||||||
|
for mut path in route.paths {
|
||||||
|
// If the last hop is probably an unannounced channel we refrain from probing all the
|
||||||
|
// way through to the end and instead probe up to the second-to-last channel.
|
||||||
|
while let Some(last_path_hop) = path.hops.last() {
|
||||||
|
if last_path_hop.maybe_announced_channel {
|
||||||
|
// We found a potentially announced last hop.
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Drop the last hop, as it's likely unannounced.
|
||||||
|
log_debug!(
|
||||||
|
self.logger,
|
||||||
|
"Avoided sending payment probe all the way to last hop {} as it is likely unannounced.",
|
||||||
|
last_path_hop.short_channel_id
|
||||||
|
);
|
||||||
|
let final_value_msat = path.final_value_msat();
|
||||||
|
path.hops.pop();
|
||||||
|
if let Some(new_last) = path.hops.last_mut() {
|
||||||
|
new_last.fee_msat += final_value_msat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.hops.len() < 2 {
|
||||||
|
log_debug!(
|
||||||
|
self.logger,
|
||||||
|
"Skipped sending payment probe over path with less than two hops."
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(first_path_hop) = path.hops.first() {
|
||||||
|
if let Some(first_hop) = first_hops.iter().find(|h| {
|
||||||
|
h.get_outbound_payment_scid() == Some(first_path_hop.short_channel_id)
|
||||||
|
}) {
|
||||||
|
let path_value = path.final_value_msat() + path.fee_msat();
|
||||||
|
let used_liquidity =
|
||||||
|
used_liquidity_map.entry(first_path_hop.short_channel_id).or_insert(0);
|
||||||
|
|
||||||
|
if first_hop.next_outbound_htlc_limit_msat
|
||||||
|
< (*used_liquidity + path_value) * liquidity_limit_multiplier
|
||||||
|
{
|
||||||
|
log_debug!(self.logger, "Skipped sending payment probe to avoid putting channel {} under the liquidity limit.", first_path_hop.short_channel_id);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
*used_liquidity += path_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.push(self.send_probe(path).map_err(|e| {
|
||||||
|
log_error!(self.logger, "Failed to send pre-flight probe: {:?}", e);
|
||||||
|
ProbeSendFailure::SendingFailed(e)
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles the generation of a funding transaction, optionally (for tests) with a function
|
/// Handles the generation of a funding transaction, optionally (for tests) with a function
|
||||||
/// which checks the correctness of the funding transaction given the associated channel.
|
/// which checks the correctness of the funding transaction given the associated channel.
|
||||||
fn funding_transaction_generated_intern<FundingOutput: Fn(&OutboundV1Channel<SP>, &Transaction) -> Result<OutPoint, APIError>>(
|
fn funding_transaction_generated_intern<FundingOutput: Fn(&OutboundV1Channel<SP>, &Transaction) -> Result<OutPoint, APIError>>(
|
||||||
|
|
|
@ -1036,7 +1036,8 @@ fn fake_network_test() {
|
||||||
short_channel_id: chan_2.0.contents.short_channel_id,
|
short_channel_id: chan_2.0.contents.short_channel_id,
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
cltv_expiry_delta: chan_3.0.contents.cltv_expiry_delta as u32
|
cltv_expiry_delta: chan_3.0.contents.cltv_expiry_delta as u32,
|
||||||
|
maybe_announced_channel: true,
|
||||||
});
|
});
|
||||||
hops.push(RouteHop {
|
hops.push(RouteHop {
|
||||||
pubkey: nodes[3].node.get_our_node_id(),
|
pubkey: nodes[3].node.get_our_node_id(),
|
||||||
|
@ -1044,7 +1045,8 @@ fn fake_network_test() {
|
||||||
short_channel_id: chan_3.0.contents.short_channel_id,
|
short_channel_id: chan_3.0.contents.short_channel_id,
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
cltv_expiry_delta: chan_4.1.contents.cltv_expiry_delta as u32
|
cltv_expiry_delta: chan_4.1.contents.cltv_expiry_delta as u32,
|
||||||
|
maybe_announced_channel: true,
|
||||||
});
|
});
|
||||||
hops.push(RouteHop {
|
hops.push(RouteHop {
|
||||||
pubkey: nodes[1].node.get_our_node_id(),
|
pubkey: nodes[1].node.get_our_node_id(),
|
||||||
|
@ -1053,6 +1055,7 @@ fn fake_network_test() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 1000000,
|
fee_msat: 1000000,
|
||||||
cltv_expiry_delta: TEST_FINAL_CLTV,
|
cltv_expiry_delta: TEST_FINAL_CLTV,
|
||||||
|
maybe_announced_channel: true,
|
||||||
});
|
});
|
||||||
hops[1].fee_msat = chan_4.1.contents.fee_base_msat as u64 + chan_4.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
|
hops[1].fee_msat = chan_4.1.contents.fee_base_msat as u64 + chan_4.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
|
||||||
hops[0].fee_msat = chan_3.0.contents.fee_base_msat as u64 + chan_3.0.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
|
hops[0].fee_msat = chan_3.0.contents.fee_base_msat as u64 + chan_3.0.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
|
||||||
|
@ -1067,7 +1070,8 @@ fn fake_network_test() {
|
||||||
short_channel_id: chan_4.0.contents.short_channel_id,
|
short_channel_id: chan_4.0.contents.short_channel_id,
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
cltv_expiry_delta: chan_3.1.contents.cltv_expiry_delta as u32
|
cltv_expiry_delta: chan_3.1.contents.cltv_expiry_delta as u32,
|
||||||
|
maybe_announced_channel: true,
|
||||||
});
|
});
|
||||||
hops.push(RouteHop {
|
hops.push(RouteHop {
|
||||||
pubkey: nodes[2].node.get_our_node_id(),
|
pubkey: nodes[2].node.get_our_node_id(),
|
||||||
|
@ -1075,7 +1079,8 @@ fn fake_network_test() {
|
||||||
short_channel_id: chan_3.0.contents.short_channel_id,
|
short_channel_id: chan_3.0.contents.short_channel_id,
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
cltv_expiry_delta: chan_2.1.contents.cltv_expiry_delta as u32
|
cltv_expiry_delta: chan_2.1.contents.cltv_expiry_delta as u32,
|
||||||
|
maybe_announced_channel: true,
|
||||||
});
|
});
|
||||||
hops.push(RouteHop {
|
hops.push(RouteHop {
|
||||||
pubkey: nodes[1].node.get_our_node_id(),
|
pubkey: nodes[1].node.get_our_node_id(),
|
||||||
|
@ -1084,6 +1089,7 @@ fn fake_network_test() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 1000000,
|
fee_msat: 1000000,
|
||||||
cltv_expiry_delta: TEST_FINAL_CLTV,
|
cltv_expiry_delta: TEST_FINAL_CLTV,
|
||||||
|
maybe_announced_channel: true,
|
||||||
});
|
});
|
||||||
hops[1].fee_msat = chan_2.1.contents.fee_base_msat as u64 + chan_2.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
|
hops[1].fee_msat = chan_2.1.contents.fee_base_msat as u64 + chan_2.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
|
||||||
hops[0].fee_msat = chan_3.1.contents.fee_base_msat as u64 + chan_3.1.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
|
hops[0].fee_msat = chan_3.1.contents.fee_base_msat as u64 + chan_3.1.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
|
||||||
|
|
|
@ -1014,27 +1014,27 @@ mod tests {
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
|
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
|
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
|
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
|
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
|
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
|
||||||
},
|
},
|
||||||
], blinded_tail: None }],
|
], blinded_tail: None }],
|
||||||
route_params: None,
|
route_params: None,
|
||||||
|
|
|
@ -391,7 +391,7 @@ pub enum RetryableSendFailure {
|
||||||
/// is in, see the description of individual enum states for more.
|
/// is in, see the description of individual enum states for more.
|
||||||
///
|
///
|
||||||
/// [`ChannelManager::send_payment_with_route`]: crate::ln::channelmanager::ChannelManager::send_payment_with_route
|
/// [`ChannelManager::send_payment_with_route`]: crate::ln::channelmanager::ChannelManager::send_payment_with_route
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum PaymentSendFailure {
|
pub enum PaymentSendFailure {
|
||||||
/// A parameter which was passed to send_payment was invalid, preventing us from attempting to
|
/// A parameter which was passed to send_payment was invalid, preventing us from attempting to
|
||||||
/// send the payment at all.
|
/// send the payment at all.
|
||||||
|
@ -465,6 +465,18 @@ pub(super) enum Bolt12PaymentError {
|
||||||
DuplicateInvoice,
|
DuplicateInvoice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates that we failed to send a payment probe. Further errors may be surfaced later via
|
||||||
|
/// [`Event::ProbeFailed`].
|
||||||
|
///
|
||||||
|
/// [`Event::ProbeFailed`]: crate::events::Event::ProbeFailed
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ProbeSendFailure {
|
||||||
|
/// We were unable to find a route to the destination.
|
||||||
|
RouteNotFound,
|
||||||
|
/// We failed to send the payment probes.
|
||||||
|
SendingFailed(PaymentSendFailure),
|
||||||
|
}
|
||||||
|
|
||||||
/// Information which is provided, encrypted, to the payment recipient when sending HTLCs.
|
/// Information which is provided, encrypted, to the payment recipient when sending HTLCs.
|
||||||
///
|
///
|
||||||
/// This should generally be constructed with data communicated to us from the recipient (via a
|
/// This should generally be constructed with data communicated to us from the recipient (via a
|
||||||
|
@ -1103,6 +1115,7 @@ impl OutboundPayments {
|
||||||
F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
|
F: Fn(SendAlongPathArgs) -> Result<(), APIError>,
|
||||||
{
|
{
|
||||||
let payment_id = PaymentId(entropy_source.get_secure_random_bytes());
|
let payment_id = PaymentId(entropy_source.get_secure_random_bytes());
|
||||||
|
let payment_secret = PaymentSecret(entropy_source.get_secure_random_bytes());
|
||||||
|
|
||||||
let payment_hash = probing_cookie_from_id(&payment_id, probing_cookie_secret);
|
let payment_hash = probing_cookie_from_id(&payment_id, probing_cookie_secret);
|
||||||
|
|
||||||
|
@ -1114,7 +1127,7 @@ impl OutboundPayments {
|
||||||
|
|
||||||
let route = Route { paths: vec![path], route_params: None };
|
let route = Route { paths: vec![path], route_params: None };
|
||||||
let onion_session_privs = self.add_new_pending_payment(payment_hash,
|
let onion_session_privs = self.add_new_pending_payment(payment_hash,
|
||||||
RecipientOnionFields::spontaneous_empty(), payment_id, None, &route, None, None,
|
RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None,
|
||||||
entropy_source, best_block_height)?;
|
entropy_source, best_block_height)?;
|
||||||
|
|
||||||
match self.pay_route_internal(&route, payment_hash, RecipientOnionFields::spontaneous_empty(),
|
match self.pay_route_internal(&route, payment_hash, RecipientOnionFields::spontaneous_empty(),
|
||||||
|
@ -1850,6 +1863,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None }],
|
}], blinded_tail: None }],
|
||||||
route_params: Some(route_params.clone()),
|
route_params: Some(route_params.clone()),
|
||||||
};
|
};
|
||||||
|
@ -2150,6 +2164,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: invoice.amount_msats(),
|
fee_msat: invoice.amount_msats(),
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
blinded_tail: None,
|
blinded_tail: None,
|
||||||
|
|
|
@ -25,6 +25,7 @@ use crate::ln::outbound_payment::{IDEMPOTENCY_TIMEOUT_TICKS, Retry};
|
||||||
use crate::routing::gossip::{EffectiveCapacity, RoutingFees};
|
use crate::routing::gossip::{EffectiveCapacity, RoutingFees};
|
||||||
use crate::routing::router::{get_route, Path, PaymentParameters, Route, Router, RouteHint, RouteHintHop, RouteHop, RouteParameters, find_route};
|
use crate::routing::router::{get_route, Path, PaymentParameters, Route, Router, RouteHint, RouteHintHop, RouteHop, RouteParameters, find_route};
|
||||||
use crate::routing::scoring::ChannelUsage;
|
use crate::routing::scoring::ChannelUsage;
|
||||||
|
use crate::util::config::UserConfig;
|
||||||
use crate::util::test_utils;
|
use crate::util::test_utils;
|
||||||
use crate::util::errors::APIError;
|
use crate::util::errors::APIError;
|
||||||
use crate::util::ser::Writeable;
|
use crate::util::ser::Writeable;
|
||||||
|
@ -1304,6 +1305,102 @@ fn onchain_failed_probe_yields_event() {
|
||||||
assert!(!nodes[0].node.has_pending_payments());
|
assert!(!nodes[0].node.has_pending_payments());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preflight_probes_yield_event_and_skip() {
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(5);
|
||||||
|
let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
|
||||||
|
|
||||||
|
// We alleviate the HTLC max-in-flight limit, as otherwise we'd always be limited through that.
|
||||||
|
let mut no_htlc_limit_config = test_default_channel_config();
|
||||||
|
no_htlc_limit_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
|
||||||
|
|
||||||
|
let user_configs = std::iter::repeat(no_htlc_limit_config).take(5).map(|c| Some(c)).collect::<Vec<Option<UserConfig>>>();
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &user_configs);
|
||||||
|
let nodes = create_network(5, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
// Setup channel topology:
|
||||||
|
// (30k:0)- N2 -(1M:0)
|
||||||
|
// / \
|
||||||
|
// N0 -(100k:0)-> N1 N4
|
||||||
|
// \ /
|
||||||
|
// (70k:0)- N3 -(1M:0)
|
||||||
|
//
|
||||||
|
let first_chan_update = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0).0;
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 30_000, 0);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 70_000, 0);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 1_000_000, 0);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 3, 4, 1_000_000, 0);
|
||||||
|
|
||||||
|
let mut invoice_features = Bolt11InvoiceFeatures::empty();
|
||||||
|
invoice_features.set_basic_mpp_optional();
|
||||||
|
|
||||||
|
let mut payment_params = PaymentParameters::from_node_id(nodes[4].node.get_our_node_id(), TEST_FINAL_CLTV)
|
||||||
|
.with_bolt11_features(invoice_features).unwrap();
|
||||||
|
|
||||||
|
let route_params = RouteParameters { payment_params, final_value_msat: 80_000_000 };
|
||||||
|
let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap();
|
||||||
|
|
||||||
|
// We check that only one probe was sent, the other one was skipped due to limited liquidity.
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
let log_msg = format!("Skipped sending payment probe to avoid putting channel {} under the liquidity limit.",
|
||||||
|
first_chan_update.contents.short_channel_id);
|
||||||
|
node_cfgs[0].logger.assert_log_contains("lightning::ln::channelmanager", &log_msg, 1);
|
||||||
|
|
||||||
|
let (payment_hash, payment_id) = res.first().unwrap();
|
||||||
|
|
||||||
|
// node[0] -- update_add_htlcs -> node[1]
|
||||||
|
check_added_monitors!(nodes[0], 1);
|
||||||
|
let probe_event = SendEvent::from_node(&nodes[0]);
|
||||||
|
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &probe_event.msgs[0]);
|
||||||
|
check_added_monitors!(nodes[1], 0);
|
||||||
|
commitment_signed_dance!(nodes[1], nodes[0], probe_event.commitment_msg, false);
|
||||||
|
expect_pending_htlcs_forwardable!(nodes[1]);
|
||||||
|
|
||||||
|
// node[1] -- update_add_htlcs -> node[2]
|
||||||
|
check_added_monitors!(nodes[1], 1);
|
||||||
|
let probe_event = SendEvent::from_node(&nodes[1]);
|
||||||
|
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &probe_event.msgs[0]);
|
||||||
|
check_added_monitors!(nodes[2], 0);
|
||||||
|
commitment_signed_dance!(nodes[2], nodes[1], probe_event.commitment_msg, false);
|
||||||
|
expect_pending_htlcs_forwardable!(nodes[2]);
|
||||||
|
|
||||||
|
// node[2] -- update_add_htlcs -> node[4]
|
||||||
|
check_added_monitors!(nodes[2], 1);
|
||||||
|
let probe_event = SendEvent::from_node(&nodes[2]);
|
||||||
|
nodes[4].node.handle_update_add_htlc(&nodes[2].node.get_our_node_id(), &probe_event.msgs[0]);
|
||||||
|
check_added_monitors!(nodes[4], 0);
|
||||||
|
commitment_signed_dance!(nodes[4], nodes[2], probe_event.commitment_msg, true, true);
|
||||||
|
|
||||||
|
// node[2] <- update_fail_htlcs -- node[4]
|
||||||
|
let updates = get_htlc_update_msgs!(nodes[4], nodes[2].node.get_our_node_id());
|
||||||
|
nodes[2].node.handle_update_fail_htlc(&nodes[4].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
|
||||||
|
check_added_monitors!(nodes[2], 0);
|
||||||
|
commitment_signed_dance!(nodes[2], nodes[4], updates.commitment_signed, true);
|
||||||
|
|
||||||
|
// node[1] <- update_fail_htlcs -- node[2]
|
||||||
|
let updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
|
||||||
|
nodes[1].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
|
||||||
|
check_added_monitors!(nodes[1], 0);
|
||||||
|
commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, true);
|
||||||
|
|
||||||
|
// node[0] <- update_fail_htlcs -- node[1]
|
||||||
|
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
|
||||||
|
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
|
||||||
|
check_added_monitors!(nodes[0], 0);
|
||||||
|
commitment_signed_dance!(nodes[0], nodes[1], updates.commitment_signed, false);
|
||||||
|
|
||||||
|
let mut events = nodes[0].node.get_and_clear_pending_events();
|
||||||
|
assert_eq!(events.len(), 1);
|
||||||
|
match events.drain(..).next().unwrap() {
|
||||||
|
crate::events::Event::ProbeSuccessful { payment_id: ev_pid, payment_hash: ev_ph, .. } => {
|
||||||
|
assert_eq!(*payment_id, ev_pid);
|
||||||
|
assert_eq!(*payment_hash, ev_ph);
|
||||||
|
},
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
assert!(!nodes[0].node.has_pending_payments());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn claimed_send_payment_idempotent() {
|
fn claimed_send_payment_idempotent() {
|
||||||
// Tests that `send_payment` (and friends) are (reasonably) idempotent.
|
// Tests that `send_payment` (and friends) are (reasonably) idempotent.
|
||||||
|
@ -2201,6 +2298,7 @@ fn auto_retry_partial_failure() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: amt_msat / 2,
|
fee_msat: amt_msat / 2,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
Path { hops: vec![RouteHop {
|
Path { hops: vec![RouteHop {
|
||||||
pubkey: nodes[1].node.get_our_node_id(),
|
pubkey: nodes[1].node.get_our_node_id(),
|
||||||
|
@ -2209,6 +2307,7 @@ fn auto_retry_partial_failure() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: amt_msat / 2,
|
fee_msat: amt_msat / 2,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
],
|
],
|
||||||
route_params: Some(route_params.clone()),
|
route_params: Some(route_params.clone()),
|
||||||
|
@ -2222,6 +2321,7 @@ fn auto_retry_partial_failure() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: amt_msat / 4,
|
fee_msat: amt_msat / 4,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
Path { hops: vec![RouteHop {
|
Path { hops: vec![RouteHop {
|
||||||
pubkey: nodes[1].node.get_our_node_id(),
|
pubkey: nodes[1].node.get_our_node_id(),
|
||||||
|
@ -2230,6 +2330,7 @@ fn auto_retry_partial_failure() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: amt_msat / 4,
|
fee_msat: amt_msat / 4,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
],
|
],
|
||||||
route_params: Some(route_params.clone()),
|
route_params: Some(route_params.clone()),
|
||||||
|
@ -2243,6 +2344,7 @@ fn auto_retry_partial_failure() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: amt_msat / 4,
|
fee_msat: amt_msat / 4,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
],
|
],
|
||||||
route_params: Some(route_params.clone()),
|
route_params: Some(route_params.clone()),
|
||||||
|
@ -2487,6 +2589,7 @@ fn retry_multi_path_single_failed_payment() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 10_000,
|
fee_msat: 10_000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
Path { hops: vec![RouteHop {
|
Path { hops: vec![RouteHop {
|
||||||
pubkey: nodes[1].node.get_our_node_id(),
|
pubkey: nodes[1].node.get_our_node_id(),
|
||||||
|
@ -2495,6 +2598,7 @@ fn retry_multi_path_single_failed_payment() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
|
fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
],
|
],
|
||||||
route_params: Some(route_params.clone()),
|
route_params: Some(route_params.clone()),
|
||||||
|
@ -2576,6 +2680,7 @@ fn immediate_retry_on_failure() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
|
fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
],
|
],
|
||||||
route_params: Some(RouteParameters::from_payment_params_and_value(
|
route_params: Some(RouteParameters::from_payment_params_and_value(
|
||||||
|
@ -2662,6 +2767,7 @@ fn no_extra_retries_on_back_to_back_fail() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
|
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}, RouteHop {
|
}, RouteHop {
|
||||||
pubkey: nodes[2].node.get_our_node_id(),
|
pubkey: nodes[2].node.get_our_node_id(),
|
||||||
node_features: nodes[2].node.node_features(),
|
node_features: nodes[2].node.node_features(),
|
||||||
|
@ -2669,6 +2775,7 @@ fn no_extra_retries_on_back_to_back_fail() {
|
||||||
channel_features: nodes[2].node.channel_features(),
|
channel_features: nodes[2].node.channel_features(),
|
||||||
fee_msat: 100_000_000,
|
fee_msat: 100_000_000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
Path { hops: vec![RouteHop {
|
Path { hops: vec![RouteHop {
|
||||||
pubkey: nodes[1].node.get_our_node_id(),
|
pubkey: nodes[1].node.get_our_node_id(),
|
||||||
|
@ -2677,6 +2784,7 @@ fn no_extra_retries_on_back_to_back_fail() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
|
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}, RouteHop {
|
}, RouteHop {
|
||||||
pubkey: nodes[2].node.get_our_node_id(),
|
pubkey: nodes[2].node.get_our_node_id(),
|
||||||
node_features: nodes[2].node.node_features(),
|
node_features: nodes[2].node.node_features(),
|
||||||
|
@ -2684,6 +2792,7 @@ fn no_extra_retries_on_back_to_back_fail() {
|
||||||
channel_features: nodes[2].node.channel_features(),
|
channel_features: nodes[2].node.channel_features(),
|
||||||
fee_msat: 100_000_000,
|
fee_msat: 100_000_000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None }
|
}], blinded_tail: None }
|
||||||
],
|
],
|
||||||
route_params: Some(RouteParameters::from_payment_params_and_value(
|
route_params: Some(RouteParameters::from_payment_params_and_value(
|
||||||
|
@ -2862,6 +2971,7 @@ fn test_simple_partial_retry() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
|
fee_msat: 0, // nodes[1] will fail the payment as we don't pay its fee
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}, RouteHop {
|
}, RouteHop {
|
||||||
pubkey: nodes[2].node.get_our_node_id(),
|
pubkey: nodes[2].node.get_our_node_id(),
|
||||||
node_features: nodes[2].node.node_features(),
|
node_features: nodes[2].node.node_features(),
|
||||||
|
@ -2869,6 +2979,7 @@ fn test_simple_partial_retry() {
|
||||||
channel_features: nodes[2].node.channel_features(),
|
channel_features: nodes[2].node.channel_features(),
|
||||||
fee_msat: 100_000_000,
|
fee_msat: 100_000_000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
Path { hops: vec![RouteHop {
|
Path { hops: vec![RouteHop {
|
||||||
pubkey: nodes[1].node.get_our_node_id(),
|
pubkey: nodes[1].node.get_our_node_id(),
|
||||||
|
@ -2877,6 +2988,7 @@ fn test_simple_partial_retry() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 100_000,
|
fee_msat: 100_000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}, RouteHop {
|
}, RouteHop {
|
||||||
pubkey: nodes[2].node.get_our_node_id(),
|
pubkey: nodes[2].node.get_our_node_id(),
|
||||||
node_features: nodes[2].node.node_features(),
|
node_features: nodes[2].node.node_features(),
|
||||||
|
@ -2884,6 +2996,7 @@ fn test_simple_partial_retry() {
|
||||||
channel_features: nodes[2].node.channel_features(),
|
channel_features: nodes[2].node.channel_features(),
|
||||||
fee_msat: 100_000_000,
|
fee_msat: 100_000_000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None }
|
}], blinded_tail: None }
|
||||||
],
|
],
|
||||||
route_params: Some(RouteParameters::from_payment_params_and_value(
|
route_params: Some(RouteParameters::from_payment_params_and_value(
|
||||||
|
@ -3026,6 +3139,7 @@ fn test_threaded_payment_retries() {
|
||||||
channel_features: nodes[1].node.channel_features(),
|
channel_features: nodes[1].node.channel_features(),
|
||||||
fee_msat: 0,
|
fee_msat: 0,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}, RouteHop {
|
}, RouteHop {
|
||||||
pubkey: nodes[3].node.get_our_node_id(),
|
pubkey: nodes[3].node.get_our_node_id(),
|
||||||
node_features: nodes[2].node.node_features(),
|
node_features: nodes[2].node.node_features(),
|
||||||
|
@ -3033,6 +3147,7 @@ fn test_threaded_payment_retries() {
|
||||||
channel_features: nodes[2].node.channel_features(),
|
channel_features: nodes[2].node.channel_features(),
|
||||||
fee_msat: amt_msat / 1000,
|
fee_msat: amt_msat / 1000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None },
|
}], blinded_tail: None },
|
||||||
Path { hops: vec![RouteHop {
|
Path { hops: vec![RouteHop {
|
||||||
pubkey: nodes[2].node.get_our_node_id(),
|
pubkey: nodes[2].node.get_our_node_id(),
|
||||||
|
@ -3041,6 +3156,7 @@ fn test_threaded_payment_retries() {
|
||||||
channel_features: nodes[2].node.channel_features(),
|
channel_features: nodes[2].node.channel_features(),
|
||||||
fee_msat: 100_000,
|
fee_msat: 100_000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}, RouteHop {
|
}, RouteHop {
|
||||||
pubkey: nodes[3].node.get_our_node_id(),
|
pubkey: nodes[3].node.get_our_node_id(),
|
||||||
node_features: nodes[3].node.node_features(),
|
node_features: nodes[3].node.node_features(),
|
||||||
|
@ -3048,6 +3164,7 @@ fn test_threaded_payment_retries() {
|
||||||
channel_features: nodes[3].node.channel_features(),
|
channel_features: nodes[3].node.channel_features(),
|
||||||
fee_msat: amt_msat - amt_msat / 1000,
|
fee_msat: amt_msat - amt_msat / 1000,
|
||||||
cltv_expiry_delta: 100,
|
cltv_expiry_delta: 100,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None }
|
}], blinded_tail: None }
|
||||||
],
|
],
|
||||||
route_params: Some(RouteParameters::from_payment_params_and_value(
|
route_params: Some(RouteParameters::from_payment_params_and_value(
|
||||||
|
|
|
@ -250,10 +250,20 @@ pub struct RouteHop {
|
||||||
///
|
///
|
||||||
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
|
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
|
||||||
pub cltv_expiry_delta: u32,
|
pub cltv_expiry_delta: u32,
|
||||||
|
/// Indicates whether this hop is possibly announced in the public network graph.
|
||||||
|
///
|
||||||
|
/// Will be `true` if there is a possibility that the channel is publicly known, i.e., if we
|
||||||
|
/// either know for sure it's announced in the public graph, or if any public channels exist
|
||||||
|
/// for which the given `short_channel_id` could be an alias for. Will be `false` if we believe
|
||||||
|
/// the channel to be unannounced.
|
||||||
|
///
|
||||||
|
/// Will be `true` for objects serialized with LDK version 0.0.116 and before.
|
||||||
|
pub maybe_announced_channel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_writeable_tlv_based!(RouteHop, {
|
impl_writeable_tlv_based!(RouteHop, {
|
||||||
(0, pubkey, required),
|
(0, pubkey, required),
|
||||||
|
(1, maybe_announced_channel, (default_value, true)),
|
||||||
(2, node_features, required),
|
(2, node_features, required),
|
||||||
(4, short_channel_id, required),
|
(4, short_channel_id, required),
|
||||||
(6, channel_features, required),
|
(6, channel_features, required),
|
||||||
|
@ -2472,9 +2482,27 @@ where L::Target: Logger {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
for payment_path in selected_route {
|
for payment_path in selected_route {
|
||||||
let mut hops = Vec::with_capacity(payment_path.hops.len());
|
let mut hops = Vec::with_capacity(payment_path.hops.len());
|
||||||
|
let mut prev_hop_node_id = our_node_id;
|
||||||
for (hop, node_features) in payment_path.hops.iter()
|
for (hop, node_features) in payment_path.hops.iter()
|
||||||
.filter(|(h, _)| h.candidate.short_channel_id().is_some())
|
.filter(|(h, _)| h.candidate.short_channel_id().is_some())
|
||||||
{
|
{
|
||||||
|
let maybe_announced_channel = if let CandidateRouteHop::PublicHop { .. } = hop.candidate {
|
||||||
|
// If we sourced the hop from the graph we're sure the target node is announced.
|
||||||
|
true
|
||||||
|
} else if let CandidateRouteHop::FirstHop { details } = hop.candidate {
|
||||||
|
// If this is a first hop we also know if it's announced.
|
||||||
|
details.is_public
|
||||||
|
} else {
|
||||||
|
// If we sourced it any other way, we double-check the network graph to see if
|
||||||
|
// there are announced channels between the endpoints. If so, the hop might be
|
||||||
|
// referring to any of the announced channels, as its `short_channel_id` might be
|
||||||
|
// an alias, in which case we don't take any chances here.
|
||||||
|
network_graph.node(&hop.node_id).map_or(false, |hop_node|
|
||||||
|
hop_node.channels.iter().any(|scid| network_graph.channel(*scid)
|
||||||
|
.map_or(false, |c| c.as_directed_from(&prev_hop_node_id).is_some()))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
hops.push(RouteHop {
|
hops.push(RouteHop {
|
||||||
pubkey: PublicKey::from_slice(hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
|
pubkey: PublicKey::from_slice(hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
|
||||||
node_features: node_features.clone(),
|
node_features: node_features.clone(),
|
||||||
|
@ -2482,7 +2510,10 @@ where L::Target: Logger {
|
||||||
channel_features: hop.candidate.features(),
|
channel_features: hop.candidate.features(),
|
||||||
fee_msat: hop.fee_msat,
|
fee_msat: hop.fee_msat,
|
||||||
cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
|
cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
|
||||||
|
maybe_announced_channel,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
prev_hop_node_id = hop.node_id;
|
||||||
}
|
}
|
||||||
let mut final_cltv_delta = final_cltv_expiry_delta;
|
let mut final_cltv_delta = final_cltv_expiry_delta;
|
||||||
let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
|
let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
|
||||||
|
@ -5964,17 +5995,17 @@ mod tests {
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
|
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
|
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0
|
short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0, maybe_announced_channel: true,
|
||||||
},
|
},
|
||||||
], blinded_tail: None }],
|
], blinded_tail: None }],
|
||||||
route_params: None,
|
route_params: None,
|
||||||
|
@ -5991,23 +6022,23 @@ mod tests {
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
|
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
|
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
|
||||||
},
|
},
|
||||||
], blinded_tail: None }, Path { hops: vec![
|
], blinded_tail: None }, Path { hops: vec![
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
|
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
|
||||||
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
|
||||||
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
|
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
|
||||||
},
|
},
|
||||||
], blinded_tail: None }],
|
], blinded_tail: None }],
|
||||||
route_params: None,
|
route_params: None,
|
||||||
|
@ -6606,6 +6637,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 100,
|
fee_msat: 100,
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}],
|
}],
|
||||||
blinded_tail: Some(BlindedTail {
|
blinded_tail: Some(BlindedTail {
|
||||||
hops: blinded_path_1.blinded_hops,
|
hops: blinded_path_1.blinded_hops,
|
||||||
|
@ -6620,6 +6652,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 100,
|
fee_msat: 100,
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}], blinded_tail: None }],
|
}], blinded_tail: None }],
|
||||||
route_params: None,
|
route_params: None,
|
||||||
};
|
};
|
||||||
|
@ -6659,6 +6692,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 100,
|
fee_msat: 100,
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: false,
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: blinded_path.introduction_node_id,
|
pubkey: blinded_path.introduction_node_id,
|
||||||
|
@ -6667,6 +6701,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 1,
|
fee_msat: 1,
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: false,
|
||||||
}],
|
}],
|
||||||
blinded_tail: Some(BlindedTail {
|
blinded_tail: Some(BlindedTail {
|
||||||
hops: blinded_path.blinded_hops,
|
hops: blinded_path.blinded_hops,
|
||||||
|
@ -6699,6 +6734,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 100,
|
fee_msat: 100,
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: false,
|
||||||
},
|
},
|
||||||
RouteHop {
|
RouteHop {
|
||||||
pubkey: blinded_path.introduction_node_id,
|
pubkey: blinded_path.introduction_node_id,
|
||||||
|
@ -6707,6 +6743,7 @@ mod tests {
|
||||||
channel_features: ChannelFeatures::empty(),
|
channel_features: ChannelFeatures::empty(),
|
||||||
fee_msat: 1,
|
fee_msat: 1,
|
||||||
cltv_expiry_delta: 0,
|
cltv_expiry_delta: 0,
|
||||||
|
maybe_announced_channel: false,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
blinded_tail: Some(BlindedTail {
|
blinded_tail: Some(BlindedTail {
|
||||||
|
|
|
@ -2186,6 +2186,7 @@ mod tests {
|
||||||
channel_features: channelmanager::provided_channel_features(&config),
|
channel_features: channelmanager::provided_channel_features(&config),
|
||||||
fee_msat,
|
fee_msat,
|
||||||
cltv_expiry_delta: 18,
|
cltv_expiry_delta: 18,
|
||||||
|
maybe_announced_channel: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue