Skip to content

Commit

Permalink
Switch to upstreamed preflight probing methods
Browse files Browse the repository at this point in the history
  • Loading branch information
tnull committed Sep 25, 2023
1 parent e5650db commit 0901c19
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 88 deletions.
6 changes: 4 additions & 2 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ interface LDKNode {
[Throws=NodeError]
PaymentHash send_spontaneous_payment(u64 amount_msat, PublicKey node_id);
[Throws=NodeError]
void send_payment_probe([ByRef]Bolt11Invoice invoice);
void send_payment_probes([ByRef]Bolt11Invoice invoice);
[Throws=NodeError]
void send_spontaneous_payment_probe(u64 amount_msat, PublicKey node_id);
void send_spontaneous_payment_probes(u64 amount_msat, PublicKey node_id);
[Throws=NodeError]
void send_payment_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat);
[Throws=NodeError]
Bolt11Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
[Throws=NodeError]
Expand Down
2 changes: 1 addition & 1 deletion src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
gossip_source,
kv_store,
logger,
router,
_router: router,
scorer,
peer_store,
payment_store,
Expand Down
146 changes: 61 additions & 85 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ use types::{ChainMonitor, ChannelManager, KeysManager, NetworkGraph, PeerManager
pub use types::{ChannelDetails, PeerDetails, UserChannelId};
use wallet::Wallet;

use logger::{log_debug, log_error, log_info, log_trace, FilesystemLogger, Logger};
use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger};

use lightning::chain::Confirm;
use lightning::ln::channelmanager::{self, PaymentId, RecipientOnionFields, Retry};
Expand All @@ -138,7 +138,7 @@ use lightning_background_processor::process_events_async;

use lightning_transaction_sync::EsploraSyncClient;

use lightning::routing::router::{PaymentParameters, RouteParameters, Router as LdkRouter};
use lightning::routing::router::{PaymentParameters, RouteParameters};
use lightning_invoice::{payment, Bolt11Invoice, Currency};

use bitcoin::hashes::sha256::Hash as Sha256;
Expand All @@ -150,7 +150,6 @@ use bitcoin::{Address, Txid};

use rand::Rng;

use std::collections::HashMap;
use std::default::Default;
use std::net::ToSocketAddrs;
use std::sync::{Arc, Mutex, RwLock};
Expand Down Expand Up @@ -295,7 +294,7 @@ pub struct Node<K: KVStore + Sync + Send + 'static> {
gossip_source: Arc<GossipSource>,
kv_store: Arc<K>,
logger: Arc<FilesystemLogger>,
router: Arc<Router>,
_router: Arc<Router>,
scorer: Arc<Mutex<Scorer>>,
peer_store: Arc<PeerStore<K, Arc<FilesystemLogger>>>,
payment_store: Arc<PaymentStore<K, Arc<FilesystemLogger>>>,
Expand Down Expand Up @@ -1320,116 +1319,93 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
/// payment. To mitigate this issue, channels with available liquidity less than the required
/// amount times [`Config::probing_liquidity_limit_multiplier`] won't be used to send
/// pre-flight probes.
pub fn send_payment_probe(&self, invoice: &Bolt11Invoice) -> Result<(), Error> {
pub fn send_payment_probes(&self, invoice: &Bolt11Invoice) -> Result<(), Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let amount_msat = if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
invoice_amount_msat
} else {
log_error!(self.logger, "Failed to send probe as no amount was given in the invoice.");
return Err(Error::InvalidAmount);
};
let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier);

let expiry_time = invoice.duration_since_epoch().saturating_add(invoice.expiry_time());
let mut payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
payment::preflight_probe_invoice(
invoice,
&*self.channel_manager,
liquidity_limit_multiplier,
)
.with_expiry_time(expiry_time.as_secs())
.with_route_hints(invoice.route_hints())
.map_err(|_| Error::InvalidInvoice)?;
if let Some(features) = invoice.features() {
payment_params = payment_params
.with_bolt11_features(features.clone())
.map_err(|_| Error::InvalidInvoice)?;
}
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
.map_err(|e| {
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
Error::ProbeSendingFailed
})?;

self.send_payment_probe_internal(route_params)
Ok(())
}

/// Sends payment probes over all paths of a route that would be used to pay the given
/// amount to the given `node_id`.
///
/// 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 [`Config::probing_liquidity_limit_multiplier`] won't be used to send
/// pre-flight probes.
pub fn send_spontaneous_payment_probe(
/// See [`Self::send_payment_probes`] for more information.
pub fn send_spontaneous_payment_probes(
&self, amount_msat: u64, node_id: PublicKey,
) -> Result<(), Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let payment_params =
PaymentParameters::from_node_id(node_id, self.config.default_cltv_expiry_delta);

let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };

self.send_payment_probe_internal(route_params)
}

fn send_payment_probe_internal(&self, route_params: RouteParameters) -> Result<(), Error> {
let payer = self.channel_manager.get_our_node_id();
let usable_channels = self.channel_manager.list_usable_channels();
let first_hops = usable_channels.iter().collect::<Vec<_>>();
let inflight_htlcs = self.channel_manager.compute_inflight_htlcs();
let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier);
let cltv_expiry_delta = self.config.default_cltv_expiry_delta;

let route = self
.router
.find_route(&payer, &route_params, Some(&first_hops), inflight_htlcs)
self.channel_manager
.send_spontaneous_preflight_probes(
node_id,
amount_msat,
cltv_expiry_delta,
liquidity_limit_multiplier,
)
.map_err(|e| {
log_error!(self.logger, "Failed to find path for payment probe: {:?}", e);
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
Error::ProbeSendingFailed
})?;

let mut used_liquidity_map = HashMap::with_capacity(first_hops.len());
for path in route.paths {
if path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()) < 2 {
log_debug!(
Ok(())
}

/// Sends payment probes over all paths of a route that would be used to pay the given
/// zero-value invoice using the given amount.
///
/// This can be used to send pre-flight probes for a so-called "zero-amount" invoice, i.e., an
/// invoice that leaves the amount paid to be determined by the user.
///
/// See [`Self::send_payment_probes`] for more information.
pub fn send_payment_probes_using_amount(
&self, invoice: &Bolt11Invoice, amount_msat: u64,
) -> Result<(), Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
if amount_msat < invoice_amount_msat {
log_error!(
self.logger,
"Skipped sending payment probe over path with less than two hops."
);
continue;
"Failed to send probes as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat);
return Err(Error::InvalidAmount);
}
}

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)
* self.config.probing_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;
}
}
}
let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier);

self.channel_manager.send_probe(path).map_err(|e| {
log_error!(self.logger, "Failed to send payment probe: {:?}", e);
Error::ProbeSendingFailed
})?;
}
payment::preflight_probe_zero_value_invoice(
invoice,
amount_msat,
&*self.channel_manager,
liquidity_limit_multiplier,
)
.map_err(|e| {
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
Error::ProbeSendingFailed
})?;

Ok(())
}
Expand Down

0 comments on commit 0901c19

Please sign in to comment.