Skip to content

Commit 46b68c5

Browse files
committed
Include PaymentPathRetry data in PaymentPathFailed
When a payment path fails, it may be retried. Typically, this means re-computing the route after updating the NetworkGraph and channel scores in order to avoid the failing hop. The last hop in PaymentPathFailed's path field contains the pubkey, amount, and CLTV values needed to pass to get_route. However, it does not contain the payee's features and route hints from the invoice. Include the entire set of parameters in PaymentPathRetry and add it to the PaymentPathFailed event. Add a get_retry_route wrapper around get_route that takes PaymentPathRetry. This allows an EventHandler to retry failed payment paths using the payee's route hints and features.
1 parent 8b9cf93 commit 46b68c5

File tree

6 files changed

+108
-11
lines changed

6 files changed

+108
-11
lines changed

lightning/src/ln/channelmanager.rs

+3
Original file line numberDiff line numberDiff line change
@@ -3036,6 +3036,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30363036
all_paths_failed: payment.get().remaining_parts() == 0,
30373037
path: path.clone(),
30383038
short_channel_id: None,
3039+
retry: None,
30393040
#[cfg(test)]
30403041
error_code: None,
30413042
#[cfg(test)]
@@ -3103,6 +3104,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
31033104
all_paths_failed,
31043105
path: path.clone(),
31053106
short_channel_id,
3107+
retry: None,
31063108
#[cfg(test)]
31073109
error_code: onion_error_code,
31083110
#[cfg(test)]
@@ -3131,6 +3133,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
31313133
all_paths_failed,
31323134
path: path.clone(),
31333135
short_channel_id: Some(path.first().unwrap().short_channel_id),
3136+
retry: None,
31343137
#[cfg(test)]
31353138
error_code: Some(*failure_code),
31363139
#[cfg(test)]

lightning/src/ln/functional_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6008,7 +6008,7 @@ fn test_fail_holding_cell_htlc_upon_free() {
60086008
let events = nodes[0].node.get_and_clear_pending_events();
60096009
assert_eq!(events.len(), 1);
60106010
match &events[0] {
6011-
&Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref all_paths_failed, path: _, ref short_channel_id, ref error_code, ref error_data } => {
6011+
&Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref all_paths_failed, path: _, ref short_channel_id, retry: _, ref error_code, ref error_data } => {
60126012
assert_eq!(our_payment_hash.clone(), *payment_hash);
60136013
assert_eq!(*rejected_by_dest, false);
60146014
assert_eq!(*all_paths_failed, true);
@@ -6092,7 +6092,7 @@ fn test_free_and_fail_holding_cell_htlcs() {
60926092
let events = nodes[0].node.get_and_clear_pending_events();
60936093
assert_eq!(events.len(), 1);
60946094
match &events[0] {
6095-
&Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref all_paths_failed, path: _, ref short_channel_id, ref error_code, ref error_data } => {
6095+
&Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref all_paths_failed, path: _, ref short_channel_id, retry: _, ref error_code, ref error_data } => {
60966096
assert_eq!(payment_hash_2.clone(), *payment_hash);
60976097
assert_eq!(*rejected_by_dest, false);
60986098
assert_eq!(*all_paths_failed, true);

lightning/src/ln/onion_route_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ fn run_onion_failure_test_with_fail_intercept<F1,F2,F3>(_name: &str, test_case:
162162

163163
let events = nodes[0].node.get_and_clear_pending_events();
164164
assert_eq!(events.len(), 1);
165-
if let &Event::PaymentPathFailed { payment_hash: _, ref rejected_by_dest, ref network_update, ref all_paths_failed, path: _, ref short_channel_id, ref error_code, error_data: _ } = &events[0] {
165+
if let &Event::PaymentPathFailed { payment_hash: _, ref rejected_by_dest, ref network_update, ref all_paths_failed, path: _, ref short_channel_id, retry: _, ref error_code, error_data: _ } = &events[0] {
166166
assert_eq!(*rejected_by_dest, !expected_retryable);
167167
assert_eq!(*all_paths_failed, true);
168168
assert_eq!(*error_code, expected_error_code);

lightning/src/routing/network_graph.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,7 @@ mod tests {
18141814
msg: valid_channel_update,
18151815
}),
18161816
short_channel_id: None,
1817+
retry: None,
18171818
error_code: None,
18181819
error_data: None,
18191820
});
@@ -1840,6 +1841,7 @@ mod tests {
18401841
is_permanent: false,
18411842
}),
18421843
short_channel_id: None,
1844+
retry: None,
18431845
error_code: None,
18441846
error_data: None,
18451847
});
@@ -1864,6 +1866,7 @@ mod tests {
18641866
is_permanent: true,
18651867
}),
18661868
short_channel_id: None,
1869+
retry: None,
18671870
error_code: None,
18681871
error_data: None,
18691872
});

lightning/src/routing/router.rs

+84-5
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,31 @@ impl Readable for Route {
129129
}
130130
}
131131

132+
/// Parameters needed to re-compute a [`Route`] for retrying a failed payment path.
133+
///
134+
/// Provided in [`Event::PaymentPathFailed`] and passed to [`get_retry_route`].
135+
///
136+
/// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
137+
#[derive(Clone, Debug)]
138+
pub struct PaymentPathRetry {
139+
/// The recipient of the failed payment path.
140+
pub payee: Payee,
141+
142+
/// The amount in msats sent on the failed payment path.
143+
pub final_value_msat: u64,
144+
145+
/// The CLTV on the final hop of the failed payment path.
146+
pub final_cltv_expiry_delta: u32,
147+
}
148+
149+
impl_writeable_tlv_based!(PaymentPathRetry, {
150+
(0, payee, required),
151+
(2, final_value_msat, required),
152+
(4, final_cltv_expiry_delta, required),
153+
});
154+
132155
/// The recipient of a payment.
156+
#[derive(Clone, Debug)]
133157
pub struct Payee {
134158
/// The node id of the payee.
135159
pubkey: PublicKey,
@@ -146,6 +170,12 @@ pub struct Payee {
146170
pub route_hints: Vec<RouteHint>,
147171
}
148172

173+
impl_writeable_tlv_based!(Payee, {
174+
(0, pubkey, required),
175+
(2, features, option),
176+
(4, route_hints, vec_type),
177+
});
178+
149179
impl Payee {
150180
/// Creates a payee with the node id of the given `pubkey`.
151181
pub fn new(pubkey: PublicKey) -> Self {
@@ -180,6 +210,28 @@ impl Payee {
180210
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
181211
pub struct RouteHint(pub Vec<RouteHintHop>);
182212

213+
214+
impl Writeable for RouteHint {
215+
fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
216+
(self.0.len() as u64).write(writer)?;
217+
for hop in self.0.iter() {
218+
hop.write(writer)?;
219+
}
220+
Ok(())
221+
}
222+
}
223+
224+
impl Readable for RouteHint {
225+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
226+
let hop_count: u64 = Readable::read(reader)?;
227+
let mut hops = Vec::with_capacity(cmp::min(hop_count, 16) as usize);
228+
for _ in 0..hop_count {
229+
hops.push(Readable::read(reader)?);
230+
}
231+
Ok(Self(hops))
232+
}
233+
}
234+
183235
/// A channel descriptor for a hop along a payment path.
184236
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
185237
pub struct RouteHintHop {
@@ -197,6 +249,15 @@ pub struct RouteHintHop {
197249
pub htlc_maximum_msat: Option<u64>,
198250
}
199251

252+
impl_writeable_tlv_based!(RouteHintHop, {
253+
(0, src_node_id, required),
254+
(1, htlc_minimum_msat, option),
255+
(2, short_channel_id, required),
256+
(3, htlc_maximum_msat, option),
257+
(4, fees, required),
258+
(6, cltv_expiry_delta, required),
259+
});
260+
200261
#[derive(Eq, PartialEq)]
201262
struct RouteGraphNode {
202263
node_id: NodeId,
@@ -413,13 +474,31 @@ fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option<u64> {
413474
pub fn get_keysend_route<L: Deref, S: routing::Score>(
414475
our_node_pubkey: &PublicKey, network: &NetworkGraph, payee: &PublicKey,
415476
first_hops: Option<&[&ChannelDetails]>, last_hops: &[&RouteHint], final_value_msat: u64,
416-
final_cltv: u32, logger: L, scorer: &S
477+
final_cltv_expiry_delta: u32, logger: L, scorer: &S
417478
) -> Result<Route, LightningError>
418479
where L::Target: Logger {
419480
let route_hints = last_hops.iter().map(|hint| (*hint).clone()).collect();
420481
let payee = Payee::for_keysend(*payee).with_route_hints(route_hints);
421482
get_route(
422-
our_node_pubkey, &payee, network, first_hops, final_value_msat, final_cltv, logger, scorer
483+
our_node_pubkey, &payee, network, first_hops, final_value_msat, final_cltv_expiry_delta,
484+
logger, scorer
485+
)
486+
}
487+
488+
/// Gets a route suitable for retrying a failed payment path.
489+
///
490+
/// Used to re-compute a [`Route`] when handling a [`Event::PaymentPathFailed`]. Any adjustments to
491+
/// the [`NetworkGraph`] and channel scores should be made prior to calling this function.
492+
///
493+
/// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
494+
pub fn get_retry_route<L: Deref, S: routing::Score>(
495+
our_node_pubkey: &PublicKey, retry: &PaymentPathRetry, network: &NetworkGraph,
496+
first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S
497+
) -> Result<Route, LightningError>
498+
where L::Target: Logger {
499+
get_route(
500+
our_node_pubkey, &retry.payee, network, first_hops, retry.final_value_msat,
501+
retry.final_cltv_expiry_delta, logger, scorer
423502
)
424503
}
425504

@@ -443,8 +522,8 @@ where L::Target: Logger {
443522
/// htlc_minimum_msat/htlc_maximum_msat *are* checked as they may change based on the receiving node.
444523
pub fn get_route<L: Deref, S: routing::Score>(
445524
our_node_pubkey: &PublicKey, payee: &Payee, network: &NetworkGraph,
446-
first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, final_cltv: u32, logger: L,
447-
scorer: &S
525+
first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, final_cltv_expiry_delta: u32,
526+
logger: L, scorer: &S
448527
) -> Result<Route, LightningError>
449528
where L::Target: Logger {
450529
let payee_node_id = NodeId::from_pubkey(&payee.pubkey);
@@ -1154,7 +1233,7 @@ where L::Target: Logger {
11541233
}
11551234
ordered_hops.last_mut().unwrap().0.fee_msat = value_contribution_msat;
11561235
ordered_hops.last_mut().unwrap().0.hop_use_fee_msat = 0;
1157-
ordered_hops.last_mut().unwrap().0.cltv_expiry_delta = final_cltv;
1236+
ordered_hops.last_mut().unwrap().0.cltv_expiry_delta = final_cltv_expiry_delta;
11581237

11591238
log_trace!(logger, "Found a path back to us from the target with {} hops contributing up to {} msat: {:?}",
11601239
ordered_hops.len(), value_contribution_msat, ordered_hops);

lightning/src/util/events.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use ln::msgs::DecodeError;
2020
use ln::{PaymentPreimage, PaymentHash, PaymentSecret};
2121
use routing::network_graph::NetworkUpdate;
2222
use util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, VecReadWrapper, VecWriteWrapper};
23-
use routing::router::RouteHop;
23+
use routing::router::{PaymentPathRetry, RouteHop};
2424

2525
use bitcoin::blockdata::script::Script;
2626
use bitcoin::hashes::Hash;
@@ -216,6 +216,13 @@ pub enum Event {
216216
/// If this is `Some`, then the corresponding channel should be avoided when the payment is
217217
/// retried. May be `None` for older [`Event`] serializations.
218218
short_channel_id: Option<u64>,
219+
/// Parameters needed to re-compute a [`Route`] for retrying the failed path.
220+
///
221+
/// See [`get_retry_route`] for details.
222+
///
223+
/// [`Route`]: crate::routing::router::Route
224+
/// [`get_retry_route`]: crate::routing::router::get_retry_route
225+
retry: Option<PaymentPathRetry>,
219226
#[cfg(test)]
220227
error_code: Option<u16>,
221228
#[cfg(test)]
@@ -322,8 +329,9 @@ impl Writeable for Event {
322329
(1, payment_hash, required),
323330
});
324331
},
325-
&Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update,
326-
ref all_paths_failed, ref path, ref short_channel_id,
332+
&Event::PaymentPathFailed {
333+
ref payment_hash, ref rejected_by_dest, ref network_update,
334+
ref all_paths_failed, ref path, ref short_channel_id, ref retry,
327335
#[cfg(test)]
328336
ref error_code,
329337
#[cfg(test)]
@@ -341,6 +349,7 @@ impl Writeable for Event {
341349
(3, all_paths_failed, required),
342350
(5, path, vec_type),
343351
(7, short_channel_id, option),
352+
(9, retry, option),
344353
});
345354
},
346355
&Event::PendingHTLCsForwardable { time_forwardable: _ } => {
@@ -452,13 +461,15 @@ impl MaybeReadable for Event {
452461
let mut all_paths_failed = Some(true);
453462
let mut path: Option<Vec<RouteHop>> = Some(vec![]);
454463
let mut short_channel_id = None;
464+
let mut retry = None;
455465
read_tlv_fields!(reader, {
456466
(0, payment_hash, required),
457467
(1, network_update, ignorable),
458468
(2, rejected_by_dest, required),
459469
(3, all_paths_failed, option),
460470
(5, path, vec_type),
461471
(7, short_channel_id, ignorable),
472+
(9, retry, option),
462473
});
463474
Ok(Some(Event::PaymentPathFailed {
464475
payment_hash,
@@ -467,6 +478,7 @@ impl MaybeReadable for Event {
467478
all_paths_failed: all_paths_failed.unwrap(),
468479
path: path.unwrap(),
469480
short_channel_id,
481+
retry,
470482
#[cfg(test)]
471483
error_code,
472484
#[cfg(test)]

0 commit comments

Comments
 (0)