Skip to content

Commit 0e83e91

Browse files
authored
Merge pull request #2576 from valentinewallace/2023-09-fix-outbound-bp-fail-ev
Fix `PaymentPathFailed::payment_failed_permanently` on blinded path fail
2 parents a87f381 + 6299f7d commit 0e83e91

File tree

3 files changed

+82
-23
lines changed

3 files changed

+82
-23
lines changed

lightning/src/ln/onion_route_tests.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -645,8 +645,34 @@ fn test_onion_failure() {
645645
}, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None,
646646
Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }),
647647
Some(channels[1].0.contents.short_channel_id));
648-
run_onion_failure_test_with_fail_intercept("0-length channel update in UPDATE onion failure", 200, &nodes,
649-
&route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
648+
run_onion_failure_test_with_fail_intercept("0-length channel update in intermediate node UPDATE onion failure",
649+
100, &nodes, &route, &payment_hash, &payment_secret, |msg| {
650+
msg.amount_msat -= 1;
651+
}, |msg| {
652+
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
653+
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
654+
let mut decoded_err_packet = msgs::DecodedOnionErrorPacket {
655+
failuremsg: vec![
656+
0x10, 0x7, // UPDATE|7
657+
0x0, 0x0 // 0-len channel update
658+
],
659+
pad: vec![0; 255 - 4 /* 4-byte error message */],
660+
hmac: [0; 32],
661+
};
662+
let um = onion_utils::gen_um_from_shared_secret(&onion_keys[0].shared_secret.as_ref());
663+
let mut hmac = HmacEngine::<Sha256>::new(&um);
664+
hmac.input(&decoded_err_packet.encode()[32..]);
665+
decoded_err_packet.hmac = Hmac::from_engine(hmac).into_inner();
666+
msg.reason = onion_utils::encrypt_failure_packet(
667+
&onion_keys[0].shared_secret.as_ref(), &decoded_err_packet.encode()[..])
668+
}, || {}, true, Some(0x1000|7),
669+
Some(NetworkUpdate::ChannelFailure {
670+
short_channel_id: channels[1].0.contents.short_channel_id,
671+
is_permanent: false,
672+
}),
673+
Some(channels[1].0.contents.short_channel_id));
674+
run_onion_failure_test_with_fail_intercept("0-length channel update in final node UPDATE onion failure",
675+
200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| {
650676
let session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
651677
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
652678
let mut decoded_err_packet = msgs::DecodedOnionErrorPacket {

lightning/src/ln/onion_utils.rs

+47-15
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type:
426426
pub(crate) struct DecodedOnionFailure {
427427
pub(crate) network_update: Option<NetworkUpdate>,
428428
pub(crate) short_channel_id: Option<u64>,
429-
pub(crate) payment_retryable: bool,
429+
pub(crate) payment_failed_permanently: bool,
430430
#[cfg(test)]
431431
pub(crate) onion_error_code: Option<u16>,
432432
#[cfg(test)]
@@ -444,7 +444,14 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
444444
} = htlc_source {
445445
(path, session_priv, first_hop_htlc_msat)
446446
} else { unreachable!() };
447-
let mut res = None;
447+
448+
// Learnings from the HTLC failure to inform future payment retries and scoring.
449+
struct FailureLearnings {
450+
network_update: Option<NetworkUpdate>,
451+
short_channel_id: Option<u64>,
452+
payment_failed_permanently: bool,
453+
}
454+
let mut res: Option<FailureLearnings> = None;
448455
let mut htlc_msat = *first_hop_htlc_msat;
449456
let mut error_code_ret = None;
450457
let mut error_packet_ret = None;
@@ -467,11 +474,33 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
467474
// Got an error from within a blinded route.
468475
error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
469476
error_packet_ret = Some(vec![0; 32]);
470-
is_from_final_node = false;
477+
res = Some(FailureLearnings {
478+
network_update: None, short_channel_id: None, payment_failed_permanently: false
479+
});
471480
return
472481
},
473482
};
474483

484+
// The failing hop includes either the inbound channel to the recipient or the outbound channel
485+
// from the current hop (i.e., the next hop's inbound channel).
486+
let num_blinded_hops = path.blinded_tail.as_ref().map_or(0, |bt| bt.hops.len());
487+
// For 1-hop blinded paths, the final `path.hops` entry is the recipient.
488+
is_from_final_node = route_hop_idx + 1 == path.hops.len() && num_blinded_hops <= 1;
489+
let failing_route_hop = if is_from_final_node { route_hop } else {
490+
match path.hops.get(route_hop_idx + 1) {
491+
Some(hop) => hop,
492+
None => {
493+
// The failing hop is within a multi-hop blinded path.
494+
error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding
495+
error_packet_ret = Some(vec![0; 32]);
496+
res = Some(FailureLearnings {
497+
network_update: None, short_channel_id: None, payment_failed_permanently: false
498+
});
499+
return
500+
}
501+
}
502+
};
503+
475504
let amt_to_forward = htlc_msat - route_hop.fee_msat;
476505
htlc_msat = amt_to_forward;
477506

@@ -483,11 +512,6 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
483512
chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
484513
packet_decrypted = decryption_tmp;
485514

486-
// The failing hop includes either the inbound channel to the recipient or the outbound channel
487-
// from the current hop (i.e., the next hop's inbound channel).
488-
is_from_final_node = route_hop_idx + 1 == path.hops.len();
489-
let failing_route_hop = if is_from_final_node { route_hop } else { &path.hops[route_hop_idx + 1] };
490-
491515
let err_packet = match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
492516
Ok(p) => p,
493517
Err(_) => return
@@ -507,7 +531,9 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
507531
is_permanent: true,
508532
});
509533
let short_channel_id = Some(route_hop.short_channel_id);
510-
res = Some((network_update, short_channel_id, !is_from_final_node));
534+
res = Some(FailureLearnings {
535+
network_update, short_channel_id, payment_failed_permanently: is_from_final_node
536+
});
511537
return
512538
}
513539
};
@@ -615,8 +641,9 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
615641
} else {
616642
// The node in question intentionally encoded a 0-length channel update. This is
617643
// likely due to https://github.com/ElementsProject/lightning/issues/6200.
644+
short_channel_id = Some(failing_route_hop.short_channel_id);
618645
network_update = Some(NetworkUpdate::ChannelFailure {
619-
short_channel_id: route_hop.short_channel_id,
646+
short_channel_id: failing_route_hop.short_channel_id,
620647
is_permanent: false,
621648
});
622649
}
@@ -659,7 +686,10 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
659686
short_channel_id = Some(route_hop.short_channel_id);
660687
}
661688

662-
res = Some((network_update, short_channel_id, !(error_code & PERM == PERM && is_from_final_node)));
689+
res = Some(FailureLearnings {
690+
network_update, short_channel_id,
691+
payment_failed_permanently: error_code & PERM == PERM && is_from_final_node
692+
});
663693

664694
let (description, title) = errors::get_onion_error_description(error_code);
665695
if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size {
@@ -668,9 +698,11 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
668698
log_info!(logger, "Onion Error[from {}: {}({:#x})] {}", route_hop.pubkey, title, error_code, description);
669699
}
670700
}).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
671-
if let Some((network_update, short_channel_id, payment_retryable)) = res {
701+
if let Some(FailureLearnings {
702+
network_update, short_channel_id, payment_failed_permanently
703+
}) = res {
672704
DecodedOnionFailure {
673-
network_update, short_channel_id, payment_retryable,
705+
network_update, short_channel_id, payment_failed_permanently,
674706
#[cfg(test)]
675707
onion_error_code: error_code_ret,
676708
#[cfg(test)]
@@ -680,7 +712,7 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
680712
// only not set either packet unparseable or hmac does not match with any
681713
// payment not retryable only when garbage is from the final node
682714
DecodedOnionFailure {
683-
network_update: None, short_channel_id: None, payment_retryable: !is_from_final_node,
715+
network_update: None, short_channel_id: None, payment_failed_permanently: is_from_final_node,
684716
#[cfg(test)]
685717
onion_error_code: None,
686718
#[cfg(test)]
@@ -824,7 +856,7 @@ impl HTLCFailReason {
824856
if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source {
825857
DecodedOnionFailure {
826858
network_update: None,
827-
payment_retryable: true,
859+
payment_failed_permanently: false,
828860
short_channel_id: Some(path.hops[0].short_channel_id),
829861
#[cfg(test)]
830862
onion_error_code: Some(*failure_code),

lightning/src/ln/outbound_payment.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -1497,10 +1497,11 @@ impl OutboundPayments {
14971497
) -> bool where L::Target: Logger {
14981498
#[cfg(test)]
14991499
let DecodedOnionFailure {
1500-
network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data
1500+
network_update, short_channel_id, payment_failed_permanently, onion_error_code,
1501+
onion_error_data
15011502
} = onion_error.decode_onion_failure(secp_ctx, logger, &source);
15021503
#[cfg(not(test))]
1503-
let DecodedOnionFailure { network_update, short_channel_id, payment_retryable } =
1504+
let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently } =
15041505
onion_error.decode_onion_failure(secp_ctx, logger, &source);
15051506

15061507
let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret);
@@ -1541,8 +1542,8 @@ impl OutboundPayments {
15411542
payment.get_mut().insert_previously_failed_scid(scid);
15421543
}
15431544

1544-
if payment_is_probe || !is_retryable_now || !payment_retryable {
1545-
let reason = if !payment_retryable {
1545+
if payment_is_probe || !is_retryable_now || payment_failed_permanently {
1546+
let reason = if payment_failed_permanently {
15461547
PaymentFailureReason::RecipientRejected
15471548
} else {
15481549
PaymentFailureReason::RetriesExhausted
@@ -1572,7 +1573,7 @@ impl OutboundPayments {
15721573

15731574
let path_failure = {
15741575
if payment_is_probe {
1575-
if !payment_retryable {
1576+
if payment_failed_permanently {
15761577
events::Event::ProbeSuccessful {
15771578
payment_id: *payment_id,
15781579
payment_hash: payment_hash.clone(),
@@ -1596,7 +1597,7 @@ impl OutboundPayments {
15961597
events::Event::PaymentPathFailed {
15971598
payment_id: Some(*payment_id),
15981599
payment_hash: payment_hash.clone(),
1599-
payment_failed_permanently: !payment_retryable,
1600+
payment_failed_permanently,
16001601
failure: events::PathFailure::OnPath { network_update },
16011602
path: path.clone(),
16021603
short_channel_id,

0 commit comments

Comments
 (0)