Skip to content

Commit bcb01f6

Browse files
authored
Merge pull request #147 from tnull/2023-08-pre-flight-probing
Add pre-flight probing capabilities
2 parents 0acfddf + 07364b8 commit bcb01f6

File tree

4 files changed

+107
-4
lines changed

4 files changed

+107
-4
lines changed

bindings/ldk_node.udl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ interface LDKNode {
7272
[Throws=NodeError]
7373
PaymentHash send_spontaneous_payment(u64 amount_msat, PublicKey node_id);
7474
[Throws=NodeError]
75+
void send_payment_probe([ByRef]Invoice invoice);
76+
[Throws=NodeError]
77+
void send_spontaneous_payment_probe(u64 amount_msat, PublicKey node_id);
78+
[Throws=NodeError]
7579
Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
7680
[Throws=NodeError]
7781
Invoice receive_variable_amount_payment([ByRef]string description, u32 expiry_secs);
@@ -94,6 +98,7 @@ enum NodeError {
9498
"ConnectionFailed",
9599
"InvoiceCreationFailed",
96100
"PaymentSendingFailed",
101+
"ProbeSendingFailed",
97102
"ChannelCreationFailed",
98103
"ChannelClosingFailed",
99104
"ChannelConfigUpdateFailed",

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
730730
gossip_source,
731731
kv_store,
732732
logger,
733+
router,
733734
scorer,
734735
peer_store,
735736
payment_store,

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub enum Error {
1515
InvoiceCreationFailed,
1616
/// Sending a payment has failed.
1717
PaymentSendingFailed,
18+
/// Sending a payment probe has failed.
19+
ProbeSendingFailed,
1820
/// A channel could not be opened.
1921
ChannelCreationFailed,
2022
/// A channel could not be closed.
@@ -72,6 +74,7 @@ impl fmt::Display for Error {
7274
Self::ConnectionFailed => write!(f, "Network connection closed."),
7375
Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."),
7476
Self::PaymentSendingFailed => write!(f, "Failed to send the given payment."),
77+
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
7578
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
7679
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
7780
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),

src/lib.rs

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ use io::KVStore;
118118
use payment_store::PaymentStore;
119119
pub use payment_store::{PaymentDetails, PaymentDirection, PaymentStatus};
120120
use peer_store::{PeerInfo, PeerStore};
121-
use types::{ChainMonitor, ChannelManager, KeysManager, NetworkGraph, PeerManager, Scorer};
121+
use types::{ChainMonitor, ChannelManager, KeysManager, NetworkGraph, PeerManager, Router, Scorer};
122122
pub use types::{ChannelDetails, ChannelId, PeerDetails, UserChannelId};
123123
use wallet::Wallet;
124124

125-
use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger};
125+
use logger::{log_debug, log_error, log_info, log_trace, FilesystemLogger, Logger};
126126

127127
use lightning::chain::keysinterface::EntropySource;
128128
use lightning::chain::Confirm;
@@ -136,7 +136,7 @@ use lightning_background_processor::process_events_async;
136136

137137
use lightning_transaction_sync::EsploraSyncClient;
138138

139-
use lightning::routing::router::{PaymentParameters, RouteParameters};
139+
use lightning::routing::router::{PaymentParameters, RouteParameters, Router as LdkRouter};
140140
use lightning_invoice::{payment, Currency, Invoice};
141141

142142
use bitcoin::hashes::sha256::Hash as Sha256;
@@ -284,6 +284,7 @@ pub struct Node<K: KVStore + Sync + Send + 'static> {
284284
gossip_source: Arc<GossipSource>,
285285
kv_store: Arc<K>,
286286
logger: Arc<FilesystemLogger>,
287+
router: Arc<Router>,
287288
scorer: Arc<Mutex<Scorer>>,
288289
peer_store: Arc<PeerStore<K, Arc<FilesystemLogger>>>,
289290
payment_store: Arc<PaymentStore<K, Arc<FilesystemLogger>>>,
@@ -1035,7 +1036,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
10351036
.map_err(|_| Error::ChannelConfigUpdateFailed)
10361037
}
10371038

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

1291+
/// Sends payment probes over all paths of a route that would be used to pay the given invoice.
1292+
///
1293+
/// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting
1294+
/// the actual payment. Note this is only useful if there likely is sufficient time for the
1295+
/// probe to settle before sending out the actual payment, e.g., when waiting for user
1296+
/// confirmation in a wallet UI.
1297+
pub fn send_payment_probe(&self, invoice: &Invoice) -> Result<(), Error> {
1298+
let rt_lock = self.runtime.read().unwrap();
1299+
if rt_lock.is_none() {
1300+
return Err(Error::NotRunning);
1301+
}
1302+
1303+
let amount_msat = if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
1304+
invoice_amount_msat
1305+
} else {
1306+
log_error!(self.logger, "Failed to send probe as no amount was given in the invoice.");
1307+
return Err(Error::InvalidAmount);
1308+
};
1309+
1310+
let expiry_time = invoice.duration_since_epoch().saturating_add(invoice.expiry_time());
1311+
let mut payment_params = PaymentParameters::from_node_id(
1312+
invoice.recover_payee_pub_key(),
1313+
invoice.min_final_cltv_expiry_delta() as u32,
1314+
)
1315+
.with_expiry_time(expiry_time.as_secs())
1316+
.with_route_hints(invoice.route_hints());
1317+
if let Some(features) = invoice.features() {
1318+
payment_params = payment_params.with_features(features.clone());
1319+
}
1320+
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
1321+
1322+
self.send_payment_probe_internal(route_params)
1323+
}
1324+
1325+
/// Sends payment probes over all paths of a route that would be used to pay the given
1326+
/// amount to the given `node_id`.
1327+
///
1328+
/// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting
1329+
/// the actual payment. Note this is only useful if there likely is sufficient time for the
1330+
/// probe to settle before sending out the actual payment, e.g., when waiting for user
1331+
/// confirmation in a wallet UI.
1332+
pub fn send_spontaneous_payment_probe(
1333+
&self, amount_msat: u64, node_id: PublicKey,
1334+
) -> Result<(), Error> {
1335+
let rt_lock = self.runtime.read().unwrap();
1336+
if rt_lock.is_none() {
1337+
return Err(Error::NotRunning);
1338+
}
1339+
1340+
let payment_params =
1341+
PaymentParameters::from_node_id(node_id, self.config.default_cltv_expiry_delta);
1342+
1343+
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
1344+
1345+
self.send_payment_probe_internal(route_params)
1346+
}
1347+
1348+
fn send_payment_probe_internal(&self, route_params: RouteParameters) -> Result<(), Error> {
1349+
let payer = self.channel_manager.get_our_node_id();
1350+
let first_hops = self.channel_manager.list_usable_channels();
1351+
let inflight_htlcs = self.channel_manager.compute_inflight_htlcs();
1352+
1353+
let route = self
1354+
.router
1355+
.find_route(
1356+
&payer,
1357+
&route_params,
1358+
Some(&first_hops.iter().collect::<Vec<_>>()),
1359+
&inflight_htlcs,
1360+
)
1361+
.map_err(|e| {
1362+
log_error!(self.logger, "Failed to find path for payment probe: {:?}", e);
1363+
Error::ProbeSendingFailed
1364+
})?;
1365+
1366+
for path in route.paths {
1367+
if path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()) < 2 {
1368+
log_debug!(
1369+
self.logger,
1370+
"Skipped sending payment probe over path with less than two hops."
1371+
);
1372+
continue;
1373+
}
1374+
1375+
self.channel_manager.send_probe(path).map_err(|e| {
1376+
log_error!(self.logger, "Failed to send payment probe: {:?}", e);
1377+
Error::ProbeSendingFailed
1378+
})?;
1379+
}
1380+
1381+
Ok(())
1382+
}
1383+
12901384
/// Returns a payable invoice that can be used to request and receive a payment of the amount
12911385
/// given.
12921386
pub fn receive_payment(

0 commit comments

Comments
 (0)