Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add pre-flight probing capabilities #147

Merged
merged 3 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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(
tnull marked this conversation as resolved.
Show resolved Hide resolved
&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