Skip to content

Commit f6c9a47

Browse files
committed
Send and handle probes differently in InvoicePayer
1 parent 4793c80 commit f6c9a47

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed

lightning-invoice/src/payment.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@
7474
//! # &self, route: &Route, payment_id: PaymentId
7575
//! # ) -> Result<(), PaymentSendFailure> { unimplemented!() }
7676
//! # fn abandon_payment(&self, payment_id: PaymentId) { unimplemented!() }
77+
//! # fn send_probe_payment(
78+
//! # &self, route: &Route
79+
//! # ) -> Result<PaymentId, PaymentSendFailure> { unimplemented!() }
80+
//! # fn payment_is_probe(
81+
//! # &self, payment_hash: PaymentHash, payment_id: PaymentId
82+
//! # ) -> bool { unimplemented!() }
7783
//! # }
7884
//! #
7985
//! # struct FakeRouter {}
@@ -82,6 +88,10 @@
8288
//! # &self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash,
8389
//! # first_hops: Option<&[&ChannelDetails]>, scorer: &S
8490
//! # ) -> Result<Route, LightningError> { unimplemented!() }
91+
//! #
92+
//! # fn build_route_from_hops(
93+
//! # &self, _payer: &PublicKey, _hops: &[PublicKey], route_params: &RouteParameters
94+
//! # ) -> Result<Route, LightningError> { unimplemented!() }
8595
//! # }
8696
//! #
8797
//! # struct FakeScorer {}
@@ -247,6 +257,12 @@ pub trait Payer {
247257

248258
/// Signals that no further retries for the given payment will occur.
249259
fn abandon_payment(&self, payment_id: PaymentId);
260+
261+
/// Send a payment probe over the given [`Route`].
262+
fn send_probe_payment(&self, route: &Route) -> Result<PaymentId, PaymentSendFailure>;
263+
264+
/// Returns whether payment with the given [`PaymentId`] and [`PaymentHash`] is a probe.
265+
fn payment_is_probe(&self, payment_hash: PaymentHash, payment_id: PaymentId) -> bool;
250266
}
251267

252268
/// A trait defining behavior for routing an [`Invoice`] payment.
@@ -256,6 +272,11 @@ pub trait Router<S: Score> {
256272
&self, payer: &PublicKey, route_params: &RouteParameters, payment_hash: &PaymentHash,
257273
first_hops: Option<&[&ChannelDetails]>, scorer: &S
258274
) -> Result<Route, LightningError>;
275+
276+
/// Builds a [`Route`] from `payer` along the given path.
277+
fn build_route_from_hops(
278+
&self, payer: &PublicKey, hops: &[PublicKey], params: &RouteParameters
279+
) -> Result<Route, LightningError>;
259280
}
260281

261282
/// Strategies available to retry payment path failures for an [`Invoice`].
@@ -411,6 +432,23 @@ where
411432
.map_err(|e| { self.payment_cache.lock().unwrap().remove(&payment_hash); e })
412433
}
413434

435+
/// Sends a probe payment along the given path. The resulting payment will not be cached and
436+
/// resulting failures will be handled differently from regular payments.
437+
pub fn send_probe_along_path(
438+
&self, pubkey: PublicKey, hops: &[PublicKey], amount_msats: u64, final_cltv_expiry_delta: u32
439+
) -> Result<PaymentId, PaymentError> {
440+
let route_params = RouteParameters {
441+
payment_params: PaymentParameters::for_keysend(pubkey),
442+
final_value_msat: amount_msats,
443+
final_cltv_expiry_delta,
444+
};
445+
let payer = self.payer.node_id();
446+
let route = self.router.build_route_from_hops(&payer, hops, &route_params)
447+
.map_err(|e| PaymentError::Routing(e))?;
448+
449+
self.payer.send_probe_payment(&route).map_err(|e| PaymentError::Sending(e))
450+
}
451+
414452
fn pay_internal<F: FnOnce(&Route) -> Result<PaymentId, PaymentSendFailure> + Copy>(
415453
&self, params: &RouteParameters, payment_hash: PaymentHash, send_payment: F,
416454
) -> Result<PaymentId, PaymentError> {
@@ -550,6 +588,17 @@ where
550588
Event::PaymentPathFailed {
551589
payment_id, payment_hash, rejected_by_dest, path, short_channel_id, retry, ..
552590
} => {
591+
if let Some(payment_id) = payment_id {
592+
// When the failed payment was a probe, we make sure to not penalize the last
593+
// hop and then drop the event instead of handing it up to the user's event
594+
// handler.
595+
if self.payer.payment_is_probe(*payment_hash, *payment_id) {
596+
let path = path.iter().collect::<Vec<_>>();
597+
self.scorer.lock().payment_path_failed(&path, u64::max_value());
598+
return;
599+
}
600+
}
601+
553602
if let Some(short_channel_id) = short_channel_id {
554603
let path = path.iter().collect::<Vec<_>>();
555604
self.scorer.lock().payment_path_failed(&path, *short_channel_id);
@@ -1402,6 +1451,14 @@ mod tests {
14021451
payment_params: Some(route_params.payment_params.clone()), ..Self::route_for_value(route_params.final_value_msat)
14031452
})
14041453
}
1454+
1455+
fn build_route_from_hops(
1456+
&self, _payer: &PublicKey, _hops: &[PublicKey], route_params: &RouteParameters
1457+
) -> Result<Route, LightningError> {
1458+
Ok(Route {
1459+
payment_params: Some(route_params.payment_params.clone()), ..Self::route_for_value(route_params.final_value_msat)
1460+
})
1461+
}
14051462
}
14061463

14071464
struct FailingRouter;
@@ -1413,6 +1470,12 @@ mod tests {
14131470
) -> Result<Route, LightningError> {
14141471
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })
14151472
}
1473+
1474+
fn build_route_from_hops(
1475+
&self, _payer: &PublicKey, _hops: &[PublicKey], _route_params: &RouteParameters
1476+
) -> Result<Route, LightningError> {
1477+
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })
1478+
}
14161479
}
14171480

14181481
struct TestScorer {
@@ -1604,6 +1667,17 @@ mod tests {
16041667
}
16051668

16061669
fn abandon_payment(&self, _payment_id: PaymentId) { }
1670+
1671+
fn send_probe_payment(&self, route: &Route) -> Result<PaymentId, PaymentSendFailure> {
1672+
// TODO: for now copied from spontaneous, figure out what to do here.
1673+
self.check_value_msats(Amount::Spontaneous(route.get_total_amount()));
1674+
self.check_attempts()
1675+
}
1676+
1677+
fn payment_is_probe(&self, _payment_hash: PaymentHash, _payment_id: PaymentId) -> bool {
1678+
// TODO: figure out what to do here.
1679+
false
1680+
}
16071681
}
16081682

16091683
// *** Full Featured Functional Tests with a Real ChannelManager ***
@@ -1616,6 +1690,12 @@ mod tests {
16161690
) -> Result<Route, LightningError> {
16171691
self.0.borrow_mut().pop_front().unwrap()
16181692
}
1693+
1694+
fn build_route_from_hops(
1695+
&self, _payer: &PublicKey, _hops: &[PublicKey], _route_params: &RouteParameters
1696+
) -> Result<Route, LightningError> {
1697+
self.0.borrow_mut().pop_front().unwrap()
1698+
}
16191699
}
16201700
impl ManualRouter {
16211701
fn expect_find_route(&self, result: Result<Route, LightningError>) {

lightning-invoice/src/utils.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA};
1616
use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey};
1717
use lightning::ln::msgs::LightningError;
1818
use lightning::routing::gossip::{NetworkGraph, RoutingFees};
19-
use lightning::routing::router::{Route, RouteHint, RouteHintHop, RouteParameters, find_route};
19+
use lightning::routing::router::{Route, RouteHint, RouteHintHop, RouteParameters, find_route, build_route_from_hops};
2020
use lightning::routing::scoring::Score;
2121
use lightning::util::logger::Logger;
2222
use secp256k1::PublicKey;
@@ -469,6 +469,18 @@ where L::Target: Logger {
469469
};
470470
find_route(payer, params, &network_graph, first_hops, &*self.logger, scorer, &random_seed_bytes)
471471
}
472+
473+
fn build_route_from_hops(
474+
&self, payer: &PublicKey, hops: &[PublicKey], params: &RouteParameters
475+
) -> Result<Route, LightningError> {
476+
let network_graph = self.network_graph.read_only();
477+
let random_seed_bytes = {
478+
let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap();
479+
*locked_random_seed_bytes = sha256::Hash::hash(&*locked_random_seed_bytes).into_inner();
480+
*locked_random_seed_bytes
481+
};
482+
build_route_from_hops(payer, hops, params, &network_graph, &*self.logger, &random_seed_bytes)
483+
}
472484
}
473485

474486
impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Payer for ChannelManager<Signer, M, T, K, F, L>
@@ -509,6 +521,14 @@ where
509521
fn abandon_payment(&self, payment_id: PaymentId) {
510522
self.abandon_payment(payment_id)
511523
}
524+
525+
fn send_probe_payment(&self, route: &Route) -> Result<PaymentId, PaymentSendFailure> {
526+
self.send_probe_payment(route).map(|(_, payment_id)| payment_id)
527+
}
528+
529+
fn payment_is_probe(&self, payment_hash: PaymentHash, payment_id: PaymentId) -> bool {
530+
self.payment_is_probe(payment_hash, payment_id)
531+
}
512532
}
513533

514534
#[cfg(test)]

0 commit comments

Comments
 (0)