X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fonion_utils.rs;h=9af3de07ff4e7356ac65fe2069b3ff209761156a;hb=ce7463486ee1ae61e9af439c3d34d00244248ee9;hp=666221b2dd47fbe400b506d7602435905f9a2010;hpb=6d5c5ba4bbfd0419a284bab8d2d52cef42afbbf1;p=rust-lightning diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 666221b2..9af3de07 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -426,7 +426,7 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type: pub(crate) struct DecodedOnionFailure { pub(crate) network_update: Option, pub(crate) short_channel_id: Option, - pub(crate) payment_retryable: bool, + pub(crate) payment_failed_permanently: bool, #[cfg(test)] pub(crate) onion_error_code: Option, #[cfg(test)] @@ -444,7 +444,14 @@ pub(super) fn process_onion_failure( } = htlc_source { (path, session_priv, first_hop_htlc_msat) } else { unreachable!() }; - let mut res = None; + + // Learnings from the HTLC failure to inform future payment retries and scoring. + struct FailureLearnings { + network_update: Option, + short_channel_id: Option, + payment_failed_permanently: bool, + } + let mut res: Option = None; let mut htlc_msat = *first_hop_htlc_msat; let mut error_code_ret = None; let mut error_packet_ret = None; @@ -467,11 +474,33 @@ pub(super) fn process_onion_failure( // Got an error from within a blinded route. error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding error_packet_ret = Some(vec![0; 32]); - is_from_final_node = false; + res = Some(FailureLearnings { + network_update: None, short_channel_id: None, payment_failed_permanently: false + }); return }, }; + // The failing hop includes either the inbound channel to the recipient or the outbound channel + // from the current hop (i.e., the next hop's inbound channel). + let num_blinded_hops = path.blinded_tail.as_ref().map_or(0, |bt| bt.hops.len()); + // For 1-hop blinded paths, the final `path.hops` entry is the recipient. + is_from_final_node = route_hop_idx + 1 == path.hops.len() && num_blinded_hops <= 1; + let failing_route_hop = if is_from_final_node { route_hop } else { + match path.hops.get(route_hop_idx + 1) { + Some(hop) => hop, + None => { + // The failing hop is within a multi-hop blinded path. + error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding + error_packet_ret = Some(vec![0; 32]); + res = Some(FailureLearnings { + network_update: None, short_channel_id: None, payment_failed_permanently: false + }); + return + } + } + }; + let amt_to_forward = htlc_msat - route_hop.fee_msat; htlc_msat = amt_to_forward; @@ -483,11 +512,6 @@ pub(super) fn process_onion_failure( chacha.process(&packet_decrypted, &mut decryption_tmp[..]); packet_decrypted = decryption_tmp; - // The failing hop includes either the inbound channel to the recipient or the outbound channel - // from the current hop (i.e., the next hop's inbound channel). - is_from_final_node = route_hop_idx + 1 == path.hops.len(); - let failing_route_hop = if is_from_final_node { route_hop } else { &path.hops[route_hop_idx + 1] }; - let err_packet = match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { Ok(p) => p, Err(_) => return @@ -507,7 +531,9 @@ pub(super) fn process_onion_failure( is_permanent: true, }); let short_channel_id = Some(route_hop.short_channel_id); - res = Some((network_update, short_channel_id, !is_from_final_node)); + res = Some(FailureLearnings { + network_update, short_channel_id, payment_failed_permanently: is_from_final_node + }); return } }; @@ -615,8 +641,9 @@ pub(super) fn process_onion_failure( } else { // The node in question intentionally encoded a 0-length channel update. This is // likely due to https://github.com/ElementsProject/lightning/issues/6200. + short_channel_id = Some(failing_route_hop.short_channel_id); network_update = Some(NetworkUpdate::ChannelFailure { - short_channel_id: route_hop.short_channel_id, + short_channel_id: failing_route_hop.short_channel_id, is_permanent: false, }); } @@ -659,7 +686,10 @@ pub(super) fn process_onion_failure( short_channel_id = Some(route_hop.short_channel_id); } - res = Some((network_update, short_channel_id, !(error_code & PERM == PERM && is_from_final_node))); + res = Some(FailureLearnings { + network_update, short_channel_id, + payment_failed_permanently: error_code & PERM == PERM && is_from_final_node + }); let (description, title) = errors::get_onion_error_description(error_code); if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size { @@ -668,9 +698,11 @@ pub(super) fn process_onion_failure( log_info!(logger, "Onion Error[from {}: {}({:#x})] {}", route_hop.pubkey, title, error_code, description); } }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?"); - if let Some((network_update, short_channel_id, payment_retryable)) = res { + if let Some(FailureLearnings { + network_update, short_channel_id, payment_failed_permanently + }) = res { DecodedOnionFailure { - network_update, short_channel_id, payment_retryable, + network_update, short_channel_id, payment_failed_permanently, #[cfg(test)] onion_error_code: error_code_ret, #[cfg(test)] @@ -680,7 +712,7 @@ pub(super) fn process_onion_failure( // only not set either packet unparseable or hmac does not match with any // payment not retryable only when garbage is from the final node DecodedOnionFailure { - network_update: None, short_channel_id: None, payment_retryable: !is_from_final_node, + network_update: None, short_channel_id: None, payment_failed_permanently: is_from_final_node, #[cfg(test)] onion_error_code: None, #[cfg(test)] @@ -824,7 +856,7 @@ impl HTLCFailReason { if let &HTLCSource::OutboundRoute { ref path, .. } = htlc_source { DecodedOnionFailure { network_update: None, - payment_retryable: true, + payment_failed_permanently: false, short_channel_id: Some(path.hops[0].short_channel_id), #[cfg(test)] onion_error_code: Some(*failure_code), @@ -1014,27 +1046,27 @@ mod tests { RouteHop { pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(), channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), - short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops. + short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops. }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(), channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), - short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops. + short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops. }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(), channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), - short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops. + short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops. }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(), channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), - short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops. + short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops. }, RouteHop { pubkey: PublicKey::from_slice(&hex::decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(), channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(), - short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops. + short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops. }, ], blinded_tail: None }], route_params: None,