Skip to content

Commit c0a9e1a

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

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

lightning-invoice/src/payment.rs

Lines changed: 83 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> {
@@ -552,6 +590,20 @@ where
552590
} => {
553591
if let Some(short_channel_id) = short_channel_id {
554592
let path = path.iter().collect::<Vec<_>>();
593+
if let Some(payment_id) = payment_id {
594+
// When the failed payment was a probe, we make sure to not penalize the last
595+
// hop and then drop the event instead of handing it up to the user's event
596+
// handler.
597+
if self.payer.payment_is_probe(*payment_hash, *payment_id) {
598+
if *rejected_by_dest {
599+
self.scorer.lock().payment_path_failed(&path, u64::max_value());
600+
} else {
601+
self.scorer.lock().payment_path_failed(&path, *short_channel_id);
602+
}
603+
return;
604+
}
605+
}
606+
555607
self.scorer.lock().payment_path_failed(&path, *short_channel_id);
556608
}
557609

@@ -1402,6 +1454,14 @@ mod tests {
14021454
payment_params: Some(route_params.payment_params.clone()), ..Self::route_for_value(route_params.final_value_msat)
14031455
})
14041456
}
1457+
1458+
fn build_route_from_hops(
1459+
&self, _payer: &PublicKey, _hops: &[PublicKey], route_params: &RouteParameters
1460+
) -> Result<Route, LightningError> {
1461+
Ok(Route {
1462+
payment_params: Some(route_params.payment_params.clone()), ..Self::route_for_value(route_params.final_value_msat)
1463+
})
1464+
}
14051465
}
14061466

14071467
struct FailingRouter;
@@ -1413,6 +1473,12 @@ mod tests {
14131473
) -> Result<Route, LightningError> {
14141474
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })
14151475
}
1476+
1477+
fn build_route_from_hops(
1478+
&self, _payer: &PublicKey, _hops: &[PublicKey], _route_params: &RouteParameters
1479+
) -> Result<Route, LightningError> {
1480+
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })
1481+
}
14161482
}
14171483

14181484
struct TestScorer {
@@ -1604,6 +1670,17 @@ mod tests {
16041670
}
16051671

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

16091686
// *** Full Featured Functional Tests with a Real ChannelManager ***
@@ -1616,6 +1693,12 @@ mod tests {
16161693
) -> Result<Route, LightningError> {
16171694
self.0.borrow_mut().pop_front().unwrap()
16181695
}
1696+
1697+
fn build_route_from_hops(
1698+
&self, _payer: &PublicKey, _hops: &[PublicKey], _route_params: &RouteParameters
1699+
) -> Result<Route, LightningError> {
1700+
self.0.borrow_mut().pop_front().unwrap()
1701+
}
16191702
}
16201703
impl ManualRouter {
16211704
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)