Skip to content

Commit

Permalink
Merge pull request #147 from tnull/2023-08-pre-flight-probing
Browse files Browse the repository at this point in the history
Add pre-flight probing capabilities
  • Loading branch information
tnull authored Aug 9, 2023
2 parents 0acfddf + 07364b8 commit bcb01f6
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 4 deletions.
5 changes: 5 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ interface LDKNode {
[Throws=NodeError]
PaymentHash send_spontaneous_payment(u64 amount_msat, PublicKey node_id);
[Throws=NodeError]
void send_payment_probe([ByRef]Invoice invoice);
[Throws=NodeError]
void send_spontaneous_payment_probe(u64 amount_msat, PublicKey node_id);
[Throws=NodeError]
Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
[Throws=NodeError]
Invoice receive_variable_amount_payment([ByRef]string description, u32 expiry_secs);
Expand All @@ -94,6 +98,7 @@ enum NodeError {
"ConnectionFailed",
"InvoiceCreationFailed",
"PaymentSendingFailed",
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
gossip_source,
kv_store,
logger,
router,
scorer,
peer_store,
payment_store,
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub enum Error {
InvoiceCreationFailed,
/// Sending a payment has failed.
PaymentSendingFailed,
/// Sending a payment probe has failed.
ProbeSendingFailed,
/// A channel could not be opened.
ChannelCreationFailed,
/// A channel could not be closed.
Expand Down Expand Up @@ -72,6 +74,7 @@ impl fmt::Display for Error {
Self::ConnectionFailed => write!(f, "Network connection closed."),
Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."),
Self::PaymentSendingFailed => write!(f, "Failed to send the given payment."),
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Expand Down
102 changes: 98 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ use io::KVStore;
use payment_store::PaymentStore;
pub use payment_store::{PaymentDetails, PaymentDirection, PaymentStatus};
use peer_store::{PeerInfo, PeerStore};
use types::{ChainMonitor, ChannelManager, KeysManager, NetworkGraph, PeerManager, Scorer};
use types::{ChainMonitor, ChannelManager, KeysManager, NetworkGraph, PeerManager, Router, Scorer};
pub use types::{ChannelDetails, ChannelId, PeerDetails, UserChannelId};
use wallet::Wallet;

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

use lightning::chain::keysinterface::EntropySource;
use lightning::chain::Confirm;
Expand All @@ -136,7 +136,7 @@ use lightning_background_processor::process_events_async;

use lightning_transaction_sync::EsploraSyncClient;

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

use bitcoin::hashes::sha256::Hash as Sha256;
Expand Down Expand Up @@ -284,6 +284,7 @@ pub struct Node<K: KVStore + Sync + Send + 'static> {
gossip_source: Arc<GossipSource>,
kv_store: Arc<K>,
logger: Arc<FilesystemLogger>,
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 @@ -1035,7 +1036,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
.map_err(|_| Error::ChannelConfigUpdateFailed)
}

/// Send a payement given an invoice.
/// Send a payment given an invoice.
pub fn send_payment(&self, invoice: &Invoice) -> Result<PaymentHash, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
Expand Down Expand Up @@ -1287,6 +1288,99 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
}
}

/// Sends payment probes over all paths of a route that would be used to pay the given invoice.
///
/// 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.
pub fn send_payment_probe(&self, invoice: &Invoice) -> 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 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,
)
.with_expiry_time(expiry_time.as_secs())
.with_route_hints(invoice.route_hints());
if let Some(features) = invoice.features() {
payment_params = payment_params.with_features(features.clone());
}
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };

self.send_payment_probe_internal(route_params)
}

/// 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.
pub fn send_spontaneous_payment_probe(
&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 first_hops = self.channel_manager.list_usable_channels();
let inflight_htlcs = self.channel_manager.compute_inflight_htlcs();

let route = self
.router
.find_route(
&payer,
&route_params,
Some(&first_hops.iter().collect::<Vec<_>>()),
&inflight_htlcs,
)
.map_err(|e| {
log_error!(self.logger, "Failed to find path for payment probe: {:?}", e);
Error::ProbeSendingFailed
})?;

for path in route.paths {
if path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()) < 2 {
log_debug!(
self.logger,
"Skipped sending payment probe over path with less than two hops."
);
continue;
}

self.channel_manager.send_probe(path).map_err(|e| {
log_error!(self.logger, "Failed to send payment probe: {:?}", e);
Error::ProbeSendingFailed
})?;
}

Ok(())
}

/// Returns a payable invoice that can be used to request and receive a payment of the amount
/// given.
pub fn receive_payment(
Expand Down

0 comments on commit bcb01f6

Please sign in to comment.