diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index b8b6d967e..1254e7cb7 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -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] diff --git a/src/builder.rs b/src/builder.rs index ddf45610b..438e767a8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -753,7 +753,7 @@ fn build_with_store_internal( gossip_source, kv_store, logger, - router, + _router: router, scorer, peer_store, payment_store, diff --git a/src/lib.rs b/src/lib.rs index 093cbf31e..5a7b9cecc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; @@ -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; @@ -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}; @@ -295,7 +294,7 @@ pub struct Node { gossip_source: Arc, kv_store: Arc, logger: Arc, - router: Arc, + _router: Arc, scorer: Arc>, peer_store: Arc>>, payment_store: Arc>>, @@ -1320,52 +1319,32 @@ impl Node { /// 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(); @@ -1373,63 +1352,60 @@ impl Node { 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::>(); - 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(()) }