Onion error handling in the origin node
authorYuntai Kyong <yuntai.kyong@gmail.com>
Sun, 14 Oct 2018 13:35:26 +0000 (22:35 +0900)
committerYuntai Kyong <yuntai.kyong@gmail.com>
Sun, 14 Oct 2018 14:01:18 +0000 (23:01 +0900)
Add PaymentData to HTLCSource to reconstruct incoming_htlc_msat and cltv for each
node when an onion failure message is returned. These values are used to
check whether an updated chan_update is valid.

Refactor onion failure code and more error cases are handled

src/ln/channelmanager.rs

index 1bcf69b00bdeb7a2818648aeb64fdc6330c39948..ccb6eb295d4adc4dca484c4924ab18a037c57cdf 100644 (file)
@@ -98,6 +98,14 @@ mod channel_held_info {
                pub(super) incoming_packet_shared_secret: SharedSecret,
        }
 
+       /// First hop payment data. This can be used to reconstruct amt_to_forward
+       /// and outgoing_cltv_value for each hop combined with data from RouteHope
+       #[derive(Clone)]
+       pub struct PaymentData {
+               pub(super) htlc_msat: u64,
+               pub(super) block_height: u32,
+       }
+
        /// Tracks the inbound corresponding to an outbound HTLC
        #[derive(Clone)]
        pub enum HTLCSource {
@@ -105,6 +113,7 @@ mod channel_held_info {
                OutboundRoute {
                        route: Route,
                        session_priv: SecretKey,
+                       payment_data: PaymentData,
                },
        }
        #[cfg(test)]
@@ -113,6 +122,7 @@ mod channel_held_info {
                        HTLCSource::OutboundRoute {
                                route: Route { hops: Vec::new() },
                                session_priv: SecretKey::from_slice(&::secp256k1::Secp256k1::without_caps(), &[1; 32]).unwrap(),
+                               payment_data: PaymentData {htlc_msat: 0, block_height: 0},
                        }
                }
        }
@@ -1087,6 +1097,10 @@ impl ChannelManager {
                                chan.send_htlc_and_commit(htlc_msat, payment_hash.clone(), htlc_cltv, HTLCSource::OutboundRoute {
                                        route: route.clone(),
                                        session_priv: session_priv.clone(),
+                                       payment_data: PaymentData {
+                                               htlc_msat: htlc_msat,
+                                               block_height: cur_height,
+                                       }
                                }, onion_packet).map_err(|he| APIError::RouteError{err: he.err})?
                        };
 
@@ -1768,7 +1782,210 @@ impl ChannelManager {
                Ok(())
        }
 
+       // Process onion peacket processed in only in the origin node. Returns update
+       // for router and boolean flag indicating if payment can be retried
+       fn process_onion_failure(&self, route: &Route, mut packet_decrypted: Vec<u8>, session_priv: &SecretKey, payment_data: &PaymentData) -> Result<(Option<msgs::HTLCFailChannelUpdate>, bool), secp256k1::Error> {
+
+               macro_rules! onion_failure_log {
+                       ( $error_code_textual: expr, $error_code: expr, $reported_name: expr, $reported_value: expr ) => {
+                               log_trace!(self, "{}({}) {}({})", $error_code_textual, $error_code, $reported_name, $reported_value);
+                       };
+                       ( $error_code_textual: expr, $error_code: expr ) => {
+                               log_trace!(self, "{}({})", $error_code_textual, $error_code);
+                       };
+               }
+
+               const PERM: u16 = 0x4000;
+               const UPDATE: u16 = 0x1000;
+
+               let mut res = None;
+               let mut htlc_msat = payment_data.htlc_msat;
+               let mut outgoing_cltv_value = payment_data.block_height;
+
+               // Handle packed channel/node updates for passing back for the route handler
+               Self::construct_onion_keys_callback(&self.secp_ctx, &route, &session_priv, |shared_secret, _, _, route_hop| {
+                       if res.is_some() { return; }
+
+                       let incoming_htlc_msat = htlc_msat;
+                       let amt_to_forward = htlc_msat - route_hop.fee_msat;
+                       htlc_msat = amt_to_forward;
+                       outgoing_cltv_value += route_hop.cltv_expiry_delta;
+
+                       let ammag = ChannelManager::gen_ammag_from_shared_secret(&shared_secret);
+
+                       let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len());
+                       decryption_tmp.resize(packet_decrypted.len(), 0);
+                       let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
+                       chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
+                       packet_decrypted = decryption_tmp;
+
+                       let is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey;
+
+                       match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
+                               Err(_e) => {
+                                       res = Some((None, false));
+                               },
+                               Ok(ref err_packet) if err_packet.failuremsg.len() < 2 => {
+                                       res = Some((None, false));
+                                       // can't blaim anybody
+                               },
+                               Ok(ref err_packet) if err_packet.failuremsg.len() >= 2 => {
+                                       let um = ChannelManager::gen_um_from_shared_secret(&shared_secret);
+
+                                       let mut hmac = Hmac::new(Sha256::new(), &um);
+                                       hmac.input(&err_packet.encode()[32..]);
+                                       let mut calc_tag = [0u8; 32];
+                                       hmac.raw_result(&mut calc_tag);
+
+                                       if crypto::util::fixed_time_eq(&calc_tag, &err_packet.hmac) {
+                                               let error_code = byte_utils::slice_to_be16(&err_packet.failuremsg[0..2]);
+
+                                               match error_code & 0xff {
+                                                       1|2|3 => {
+                                                       // either from an intermediate or final node
+                                                       //   invalid_realm(PERM|1),
+                                                       //   temporary_node_failure(NODE|2)
+                                                       //   permanent_node_failure(PERM|NODE|2)
+                                                       //   required_node_feature_mssing(PERM|NODE|3)
+                                                       res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure {
+                                                               node_id: route_hop.pubkey,
+                                                               is_permanent: error_code & PERM == PERM,
+                                                       }), !(error_code & PERM == PERM && is_from_final_node)));
+                                                       // node returning invalid_realm is removed from network_map,
+                                                       // although NODE flag is not set, TODO: or remove channel only?
+                                                       // retry payment when removed node is not a final node
+                                                       return;
+                                                       },
+                                                       _ => {}
+                                               }
+
+                                               if is_from_final_node {
+                                                       let payment_retryable = match error_code {
+                                                               c if c == PERM|15 => false, // unknown_payment_hash
+                                                               c if c == PERM|16 => false, // incorrect_payment_amount
+                                                               17 => true, // final_expiry_too_soon
+                                                               18 if err_packet.failuremsg.len() == 6 => { // final_incorrect_cltv_expiry
+                                                                       let _reported_cltv_expiry = byte_utils::slice_to_be16(&err_packet.failuremsg[2..2+4]);
+                                                                       true
+                                                               },
+                                                               19 if err_packet.failuremsg.len() == 6 => { // final_incorrect_htlc_amount
+                                                                       let _reported_incoming_htlc_msat = byte_utils::slice_to_be16(&err_packet.failuremsg[2..2+4]);
+                                                                       true
+                                                               },
+                                                               _ => {
+                                                                       // A final node has sent us either an invalid code or an error_code that
+                                                                       // MUST be sent from the processing node, or the formmat of failuremsg
+                                                                       // does not coform to the spec.
+                                                                       // Remove it from the network map and don't may retry payment
+                                                                       res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure {
+                                                                               node_id: route_hop.pubkey,
+                                                                               is_permanent: true,
+                                                                       }), false));
+                                                                       return;
+                                                               }
+                                                       };
+                                                       debug_assert_eq!(payment_retryable, error_code&PERM != PERM);
+                                                       res = Some((None, payment_retryable));
+                                                       return;
+                                               }
+
+                                               // now, error_code should be only from the intermediate nodes
+                                               match error_code {
+                                                       _c if error_code & PERM == PERM => {
+                                                               res = Some((Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
+                                                                       short_channel_id: route_hop.short_channel_id,
+                                                                       is_permanent: true,
+                                                               }), false));
+                                                       },
+                                                       _c if error_code & UPDATE == UPDATE => {
+                                                               let mut pos = 2;
+                                                               pos += match error_code {
+                                                                       c if c == UPDATE|7 => 0, // temporary_channel_failure
+                                                                       c if c == UPDATE|11 => 8, // amount_below_minimum
+                                                                       c if c == UPDATE|12 => 8, // fee_insufficient
+                                                                       c if c == UPDATE|13 => 4, // incorrect_cltv_expiry
+                                                                       c if c == UPDATE|20 => 2, // channel_disabled
+                                                                       c if c == UPDATE|21 => 0, // expiry_too_far
+                                                                       _ =>  {
+                                                                               // node sending unknown code
+                                                                               res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure {
+                                                                                       node_id: route_hop.pubkey,
+                                                                                       is_permanent: true,
+                                                                               }), false));
+                                                                               return;
+                                                                       }
+                                                               };
+
+                                                               if err_packet.failuremsg.len() >= pos+2 {
+                                                                       let update_len = byte_utils::slice_to_be16(&err_packet.failuremsg[pos+2..pos+4]) as usize;
+                                                                       if err_packet.failuremsg.len() >= pos+4 + update_len {
+                                                                               if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&err_packet.failuremsg[pos+4..pos+4+update_len])) {
+                                                                                       if chan_update.contents.timestamp <= route_hop.channel_update_timestamp {
+                                                                                               res = Some((None, true));
+                                                                                               return;
+                                                                                       }
+                                                                                       // if channel_update should NOT have caused the failure:
+                                                                                       // MAY treat the channel_update as invalid.
+                                                                                       let is_chan_update_invalid = match error_code {
+                                                                                               c if c == UPDATE|11 => { // amount_below_minimum
+                                                                                                       let reported_htlc_msat = byte_utils::slice_to_be16(&err_packet.failuremsg[2..2+8]);
+                                                                                                       onion_failure_log!("amount_below_minimum", UPDATE|11, "htlc_msat", reported_htlc_msat);
+                                                                                                       incoming_htlc_msat > chan_update.contents.htlc_minimum_msat
+                                                                                               },
+                                                                                               c if c == UPDATE|12 => { // fee_insufficient
+                                                                                                       let reported_htlc_msat = byte_utils::slice_to_be16(&err_packet.failuremsg[2..2+8]);
+                                                                                                       let new_fee =  amt_to_forward.checked_mul(chan_update.contents.fee_proportional_millionths as u64).and_then(|prop_fee| { (prop_fee / 1000000).checked_add(chan_update.contents.fee_base_msat as u64) });
+                                                                                                       onion_failure_log!("fee_insufficient", UPDATE|12, "htlc_msat", reported_htlc_msat);
+                                                                                                       new_fee.is_none() || incoming_htlc_msat >= new_fee.unwrap() && incoming_htlc_msat >= amt_to_forward + new_fee.unwrap()
+                                                                                               }
+                                                                                               c if c == UPDATE|13 => { // incorrect_cltv_expiry
+                                                                                                       let reported_cltv_expiry = byte_utils::slice_to_be16(&err_packet.failuremsg[2..2+4]);
+                                                                                                       onion_failure_log!("incorrect_cltv_expiry", UPDATE|13, "cltv_expiry", reported_cltv_expiry);
+                                                                                                       route_hop.cltv_expiry_delta as u16 >= chan_update.contents.cltv_expiry_delta
+                                                                                               },
+                                                                                               c if c == UPDATE|20 => { // channel_disabled
+                                                                                                       let reported_flags = byte_utils::slice_to_be16(&err_packet.failuremsg[2..2+2]);
+                                                                                                       onion_failure_log!("channel_disabled", UPDATE|20, "flags", reported_flags);
+                                                                                                       chan_update.contents.flags & 0x01 == 0x01
+                                                                                               },
+                                                                                               c if c == UPDATE|21 => true, // expiry_too_far
+                                                                                               _ => { unreachable!(); },
+                                                                                       };
+
+                                                                                       let msg = if is_chan_update_invalid { None } else {
+                                                                                               Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage {
+                                                                                                       msg: chan_update,
+                                                                                               })
+                                                                                       };
+                                                                                       res = Some((msg, true));
+                                                                                       return;
+                                                                               }
+                                                                       }
+                                                               }
+                                                       },
+                                                       14 => { // expiry_too_soon
+                                                               res = Some((None, true));
+                                                               return;
+                                                       }
+                                                       _ => {
+                                                               // node sending unknown code
+                                                               res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure {
+                                                                       node_id: route_hop.pubkey,
+                                                                       is_permanent: true,
+                                                               }), false));
+                                                               return;
+                                                       }
+                                               }
+                                       }
+                               },
+                               _ => { unreachable!() }
+                       }
+               })?;
+               Ok(res.unwrap())
+       }
+
        fn internal_update_fail_htlc(&self, their_node_id: &PublicKey, msg: &msgs::UpdateFailHTLC) -> Result<Option<msgs::HTLCFailChannelUpdate>, MsgHandleErrInternal> {
+
                let mut channel_state = self.channel_state.lock().unwrap();
                let htlc_source = match channel_state.by_id.get_mut(&msg.channel_id) {
                        Some(chan) => {
@@ -1782,63 +1999,15 @@ impl ChannelManager {
                        None => return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel", msg.channel_id))
                }?;
 
-               match htlc_source {
-                       &HTLCSource::OutboundRoute { ref route, ref session_priv, .. } => {
-                               // Handle packed channel/node updates for passing back for the route handler
-                               let mut packet_decrypted = msg.reason.data.clone();
-                               let mut res = None;
-                               Self::construct_onion_keys_callback(&self.secp_ctx, &route, &session_priv, |shared_secret, _, _, route_hop| {
-                                       if res.is_some() { return; }
-
-                                       let ammag = ChannelManager::gen_ammag_from_shared_secret(&shared_secret);
-
-                                       let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len());
-                                       decryption_tmp.resize(packet_decrypted.len(), 0);
-                                       let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
-                                       chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
-                                       packet_decrypted = decryption_tmp;
-
-                                       if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
-                                               if err_packet.failuremsg.len() >= 2 {
-                                                       let um = ChannelManager::gen_um_from_shared_secret(&shared_secret);
-
-                                                       let mut hmac = Hmac::new(Sha256::new(), &um);
-                                                       hmac.input(&err_packet.encode()[32..]);
-                                                       let mut calc_tag = [0u8; 32];
-                                                       hmac.raw_result(&mut calc_tag);
-                                                       if crypto::util::fixed_time_eq(&calc_tag, &err_packet.hmac) {
-                                                               const UNKNOWN_CHAN: u16 = 0x4000|10;
-                                                               const TEMP_CHAN_FAILURE: u16 = 0x4000|7;
-                                                               match byte_utils::slice_to_be16(&err_packet.failuremsg[0..2]) {
-                                                                       TEMP_CHAN_FAILURE => {
-                                                                               if err_packet.failuremsg.len() >= 4 {
-                                                                                       let update_len = byte_utils::slice_to_be16(&err_packet.failuremsg[2..4]) as usize;
-                                                                                       if err_packet.failuremsg.len() >= 4 + update_len {
-                                                                                               if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&err_packet.failuremsg[4..4 + update_len])) {
-                                                                                                       res = Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage {
-                                                                                                               msg: chan_update,
-                                                                                                       });
-                                                                                               }
-                                                                                       }
-                                                                               }
-                                                                       },
-                                                                       UNKNOWN_CHAN => {
-                                                                               // No such next-hop. We know this came from the
-                                                                               // current node as the HMAC validated.
-                                                                               res = Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
-                                                                                       short_channel_id: route_hop.short_channel_id,
-                                                                                       is_permanent: true,
-                                                                               });
-                                                                       },
-                                                                       _ => {}, //TODO: Enumerate all of these!
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }).unwrap();
-                               Ok(res)
-                       },
-                       _ => { Ok(None) },
+               // we are the origin node and update route information
+               // also determine if the payment is retryable
+               if let &HTLCSource::OutboundRoute { ref route, ref session_priv, ref payment_data} = htlc_source {
+                       let (channel_update, _payment_retry) = self.process_onion_failure(route, msg.reason.data.clone(), session_priv, payment_data).unwrap();
+                       Ok(channel_update)
+                       // TODO: include pyament_retry info in PaymentFailed event that will be
+                       // fired when receiving revoke_and_ack
+               } else {
+                       Ok(None)
                }
        }