Skip to content

Commit 02b4f09

Browse files
committed
Add failing RouteHop and path to PaymentFailed
This will be useful for scoring channels and retrying multi-path payments.
1 parent 5006d12 commit 02b4f09

8 files changed

+68
-17
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2823,6 +2823,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
28232823
payment_hash,
28242824
rejected_by_dest: false,
28252825
network_update: None,
2826+
failing_route_hop: None,
28262827
#[cfg(test)]
28272828
error_code: None,
28282829
#[cfg(test)]
@@ -2867,9 +2868,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
28672868
match &onion_error {
28682869
&HTLCFailReason::LightningError { ref err } => {
28692870
#[cfg(test)]
2870-
let (network_update, payment_retryable, onion_error_code, onion_error_data) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
2871+
let (network_update, failing_route_hop, payment_retryable, onion_error_code, onion_error_data) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
28712872
#[cfg(not(test))]
2872-
let (network_update, payment_retryable, _, _) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
2873+
let (network_update, failing_route_hop, payment_retryable, _, _) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
28732874
// TODO: If we decided to blame ourselves (or one of our channels) in
28742875
// process_onion_failure we should close that channel as it implies our
28752876
// next-hop is needlessly blaming us!
@@ -2878,6 +2879,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
28782879
payment_hash: payment_hash.clone(),
28792880
rejected_by_dest: !payment_retryable,
28802881
network_update,
2882+
failing_route_hop,
28812883
#[cfg(test)]
28822884
error_code: onion_error_code,
28832885
#[cfg(test)]
@@ -2903,6 +2905,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
29032905
payment_hash: payment_hash.clone(),
29042906
rejected_by_dest: path.len() == 1,
29052907
network_update: None,
2908+
failing_route_hop: None,
29062909
#[cfg(test)]
29072910
error_code: Some(*failure_code),
29082911
#[cfg(test)]

lightning/src/ln/functional_test_utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,7 @@ macro_rules! expect_payment_failed_with_update {
10411041
let events = $node.node.get_and_clear_pending_events();
10421042
assert_eq!(events.len(), 1);
10431043
match events[0] {
1044-
Event::PaymentFailed { ref payment_hash, rejected_by_dest, ref network_update, ref error_code, ref error_data } => {
1044+
Event::PaymentFailed { ref payment_hash, rejected_by_dest, ref network_update, failing_route_hop: _, ref error_code, ref error_data } => {
10451045
assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
10461046
assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
10471047
assert!(error_code.is_some(), "expected error_code.is_some() = true");
@@ -1070,7 +1070,7 @@ macro_rules! expect_payment_failed {
10701070
let events = $node.node.get_and_clear_pending_events();
10711071
assert_eq!(events.len(), 1);
10721072
match events[0] {
1073-
Event::PaymentFailed { ref payment_hash, rejected_by_dest, network_update: _, ref error_code, ref error_data } => {
1073+
Event::PaymentFailed { ref payment_hash, rejected_by_dest, network_update: _, failing_route_hop: _, ref error_code, ref error_data } => {
10741074
assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
10751075
assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
10761076
assert!(error_code.is_some(), "expected error_code.is_some() = true");

lightning/src/ln/functional_tests.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5913,10 +5913,11 @@ fn test_fail_holding_cell_htlc_upon_free() {
59135913
let events = nodes[0].node.get_and_clear_pending_events();
59145914
assert_eq!(events.len(), 1);
59155915
match &events[0] {
5916-
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data } => {
5916+
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref failing_route_hop, ref error_code, ref error_data } => {
59175917
assert_eq!(our_payment_hash.clone(), *payment_hash);
59185918
assert_eq!(*rejected_by_dest, false);
59195919
assert_eq!(*network_update, None);
5920+
assert_eq!(*failing_route_hop, None);
59205921
assert_eq!(*error_code, None);
59215922
assert_eq!(*error_data, None);
59225923
},
@@ -5999,10 +6000,11 @@ fn test_free_and_fail_holding_cell_htlcs() {
59996000
let events = nodes[0].node.get_and_clear_pending_events();
60006001
assert_eq!(events.len(), 1);
60016002
match &events[0] {
6002-
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data } => {
6003+
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref failing_route_hop, ref error_code, ref error_data } => {
60036004
assert_eq!(payment_hash_2.clone(), *payment_hash);
60046005
assert_eq!(*rejected_by_dest, false);
60056006
assert_eq!(*network_update, None);
6007+
assert_eq!(*failing_route_hop, None);
60066008
assert_eq!(*error_code, None);
60076009
assert_eq!(*error_data, None);
60086010
},

lightning/src/ln/onion_route_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ fn run_onion_failure_test_with_fail_intercept<F1,F2,F3>(_name: &str, test_case:
163163

164164
let events = nodes[0].node.get_and_clear_pending_events();
165165
assert_eq!(events.len(), 1);
166-
if let &Event::PaymentFailed { payment_hash:_, ref rejected_by_dest, ref network_update, ref error_code, error_data: _ } = &events[0] {
166+
if let &Event::PaymentFailed { payment_hash:_, ref rejected_by_dest, ref network_update, failing_route_hop: _, ref error_code, error_data: _ } = &events[0] {
167167
assert_eq!(*rejected_by_dest, !expected_retryable);
168168
assert_eq!(*error_code, expected_error_code);
169169
if expected_channel_update.is_some() {

lightning/src/ln/onion_utils.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1111
use ln::channelmanager::HTLCSource;
1212
use ln::msgs;
1313
use routing::network_graph::NetworkUpdate;
14-
use routing::router::RouteHop;
14+
use routing::router::{FailingRouteHop, RouteHop};
1515
use util::chacha20::ChaCha20;
1616
use util::errors::{self, APIError};
1717
use util::ser::{Readable, Writeable, LengthCalculatingWriter};
@@ -331,7 +331,7 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type:
331331
/// OutboundRoute).
332332
/// Returns update, a boolean indicating that the payment itself failed, and the error code.
333333
#[inline]
334-
pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<NetworkUpdate>, bool, Option<u16>, Option<Vec<u8>>) where L::Target: Logger {
334+
pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<NetworkUpdate>, Option<FailingRouteHop>, bool, Option<u16>, Option<Vec<u8>>) where L::Target: Logger {
335335
if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat } = htlc_source {
336336
let mut res = None;
337337
let mut htlc_msat = *first_hop_htlc_msat;
@@ -382,6 +382,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
382382
|| error_code == 21; // Special case error 21 as the Route object is bogus, TODO: Maybe fail the node if the CLTV was reasonable?
383383

384384
let mut network_update = None;
385+
let mut failing_route_hop = None;
385386

386387
if error_code & NODE == NODE {
387388
network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: error_code & PERM == PERM });
@@ -394,6 +395,12 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
394395
is_permanent: true,
395396
})
396397
};
398+
failing_route_hop = if payment_failed { None } else {
399+
Some(FailingRouteHop {
400+
path: path.clone(),
401+
index: if is_from_final_node { route_hop_idx } else { route_hop_idx + 1 } as u16,
402+
})
403+
}
397404
}
398405
else if error_code & UPDATE == UPDATE {
399406
if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) {
@@ -447,10 +454,17 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
447454
});
448455
}
449456

457+
if failing_route_hop.is_none() {
458+
failing_route_hop = Some(FailingRouteHop {
459+
path: path.clone(),
460+
index: route_hop_idx as u16,
461+
});
462+
}
463+
450464
// TODO: Here (and a few other places) we assume that BADONION errors
451465
// are always "sourced" from the node previous to the one which failed
452466
// to decode the onion.
453-
res = Some((network_update, !(error_code & PERM == PERM && is_from_final_node)));
467+
res = Some((network_update, failing_route_hop, !(error_code & PERM == PERM && is_from_final_node)));
454468

455469
let (description, title) = errors::get_onion_error_description(error_code);
456470
if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size {
@@ -462,20 +476,25 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
462476
} else {
463477
// Useless packet that we can't use but it passed HMAC, so it
464478
// definitely came from the peer in question
465-
res = Some((Some(NetworkUpdate::NodeFailure {
479+
let network_update = Some(NetworkUpdate::NodeFailure {
466480
node_id: route_hop.pubkey,
467481
is_permanent: true,
468-
}), !is_from_final_node));
482+
});
483+
let failing_route_hop = Some(FailingRouteHop {
484+
path: path.clone(),
485+
index: route_hop_idx as u16,
486+
});
487+
res = Some((network_update, failing_route_hop, !is_from_final_node));
469488
}
470489
}
471490
}
472491
}).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
473-
if let Some((channel_update, payment_retryable)) = res {
474-
(channel_update, payment_retryable, error_code_ret, error_packet_ret)
492+
if let Some((channel_update, failing_route_hop, payment_retryable)) = res {
493+
(channel_update, failing_route_hop, payment_retryable, error_code_ret, error_packet_ret)
475494
} else {
476495
// only not set either packet unparseable or hmac does not match with any
477496
// payment not retryable only when garbage is from the final node
478-
(None, !is_from_final_node, None, None)
497+
(None, None, !is_from_final_node, None, None)
479498
}
480499
} else { unreachable!(); }
481500
}

lightning/src/routing/network_graph.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,7 @@ mod tests {
17311731
network_update: Some(NetworkUpdate::ChannelUpdateMessage {
17321732
msg: valid_channel_update,
17331733
}),
1734+
failing_route_hop: None,
17341735
error_code: None,
17351736
error_data: None,
17361737
});
@@ -1754,6 +1755,7 @@ mod tests {
17541755
short_channel_id,
17551756
is_permanent: false,
17561757
}),
1758+
failing_route_hop: None,
17571759
error_code: None,
17581760
error_data: None,
17591761
});
@@ -1775,6 +1777,7 @@ mod tests {
17751777
short_channel_id,
17761778
is_permanent: true,
17771779
}),
1780+
failing_route_hop: None,
17781781
error_code: None,
17791782
error_data: None,
17801783
});

lightning/src/routing/router.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use core::cmp;
2828
use core::ops::Deref;
2929

3030
/// A hop in a route
31-
#[derive(Clone, Hash, PartialEq, Eq)]
31+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
3232
pub struct RouteHop {
3333
/// The node_id of the node at this hop.
3434
pub pubkey: PublicKey,
@@ -71,6 +71,21 @@ pub struct Route {
7171
pub paths: Vec<Vec<RouteHop>>,
7272
}
7373

74+
/// A [`RouteHop`] responsible for a failed payment in context of the path containing it.
75+
#[derive(Clone, Debug, Eq, PartialEq)]
76+
pub struct FailingRouteHop {
77+
/// A path that included the failing [`RouteHop`].
78+
pub path: Vec<RouteHop>,
79+
80+
/// The index of the failing [`RouteHop`] within the path.
81+
pub index: u16,
82+
}
83+
84+
impl_writeable_tlv_based!(FailingRouteHop, {
85+
(0, path, vec_type),
86+
(2, index, required),
87+
});
88+
7489
const SERIALIZATION_VERSION: u8 = 1;
7590
const MIN_SERIALIZATION_VERSION: u8 = 1;
7691

lightning/src/util/events.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use chain::keysinterface::SpendableOutputDescriptor;
1818
use ln::msgs;
1919
use ln::{PaymentPreimage, PaymentHash, PaymentSecret};
2020
use routing::network_graph::NetworkUpdate;
21+
use routing::router::FailingRouteHop;
2122
use util::ser::{Writeable, Writer, MaybeReadable, Readable, VecReadWrapper, VecWriteWrapper};
2223

2324
use bitcoin::blockdata::script::Script;
@@ -138,6 +139,10 @@ pub enum Event {
138139
/// [`NetworkGraph`]: crate::routing::network_graph::NetworkGraph
139140
/// [`NetGraphMsgHandler`]: crate::routing::network_graph::NetGraphMsgHandler
140141
network_update: Option<NetworkUpdate>,
142+
/// The [`RouteHop`] responsible for the payment failure.
143+
///
144+
/// [[`RouteHop`]: crate::routing::router::FailingRouteHop
145+
failing_route_hop: Option<FailingRouteHop>,
141146
#[cfg(test)]
142147
error_code: Option<u16>,
143148
#[cfg(test)]
@@ -221,7 +226,7 @@ impl Writeable for Event {
221226
(0, payment_preimage, required),
222227
});
223228
},
224-
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update,
229+
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref failing_route_hop,
225230
#[cfg(test)]
226231
ref error_code,
227232
#[cfg(test)]
@@ -236,6 +241,7 @@ impl Writeable for Event {
236241
(0, payment_hash, required),
237242
(1, network_update, option),
238243
(2, rejected_by_dest, required),
244+
(3, failing_route_hop, option),
239245
});
240246
},
241247
&Event::PendingHTLCsForwardable { time_forwardable: _ } => {
@@ -319,15 +325,18 @@ impl MaybeReadable for Event {
319325
let mut payment_hash = PaymentHash([0; 32]);
320326
let mut rejected_by_dest = false;
321327
let mut network_update = None;
328+
let mut failing_route_hop = None;
322329
read_tlv_fields!(reader, {
323330
(0, payment_hash, required),
324331
(1, network_update, ignorable),
325332
(2, rejected_by_dest, required),
333+
(3, failing_route_hop, ignorable),
326334
});
327335
Ok(Some(Event::PaymentFailed {
328336
payment_hash,
329337
rejected_by_dest,
330338
network_update,
339+
failing_route_hop,
331340
#[cfg(test)]
332341
error_code,
333342
#[cfg(test)]

0 commit comments

Comments
 (0)