From: Jeffrey Czyz Date: Thu, 16 Sep 2021 04:22:44 +0000 (-0500) Subject: Add failing short channel id to PaymentPathFailed X-Git-Tag: v0.0.102~7^2 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=202acd9e16f89ffa9d0e02a3c18ce277952bb2a8;p=rust-lightning Add failing short channel id to PaymentPathFailed This will be useful for scoring channels when a payment fails. --- diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a4684bfe6..e2584c471 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2888,6 +2888,7 @@ impl ChannelMana network_update: None, all_paths_failed: sessions.get().len() == 0, path: path.clone(), + short_channel_id: None, #[cfg(test)] error_code: None, #[cfg(test)] @@ -2945,9 +2946,9 @@ impl ChannelMana match &onion_error { &HTLCFailReason::LightningError { ref err } => { #[cfg(test)] - 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()); + let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone()); #[cfg(not(test))] - let (network_update, payment_retryable, _, _) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone()); + let (network_update, short_channel_id, payment_retryable, _, _) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone()); // TODO: If we decided to blame ourselves (or one of our channels) in // process_onion_failure we should close that channel as it implies our // next-hop is needlessly blaming us! @@ -2958,6 +2959,7 @@ impl ChannelMana network_update, all_paths_failed, path: path.clone(), + short_channel_id, #[cfg(test)] error_code: onion_error_code, #[cfg(test)] @@ -2985,6 +2987,7 @@ impl ChannelMana network_update: None, all_paths_failed, path: path.clone(), + short_channel_id: Some(path.first().unwrap().short_channel_id), #[cfg(test)] error_code: Some(*failure_code), #[cfg(test)] diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 3be75abbb..8d0bb1ec3 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -6068,11 +6068,12 @@ fn test_fail_holding_cell_htlc_upon_free() { let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); match &events[0] { - &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed, path: _ } => { + &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 } => { assert_eq!(our_payment_hash.clone(), *payment_hash); assert_eq!(*rejected_by_dest, false); assert_eq!(*all_paths_failed, true); assert_eq!(*network_update, None); + assert_eq!(*short_channel_id, None); assert_eq!(*error_code, None); assert_eq!(*error_data, None); }, @@ -6155,11 +6156,12 @@ fn test_free_and_fail_holding_cell_htlcs() { let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); match &events[0] { - &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed, path: _ } => { + &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 } => { assert_eq!(payment_hash_2.clone(), *payment_hash); assert_eq!(*rejected_by_dest, false); assert_eq!(*all_paths_failed, true); assert_eq!(*network_update, None); + assert_eq!(*short_channel_id, None); assert_eq!(*error_code, None); assert_eq!(*error_data, None); }, diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 4ad15bd42..cf5be469c 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -40,11 +40,11 @@ use core::default::Default; use ln::functional_test_utils::*; -fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option, expected_channel_update: Option) +fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option, expected_channel_update: Option, expected_short_channel_id: Option) where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), F2: FnMut(), { - run_onion_failure_test_with_fail_intercept(_name, test_case, nodes, route, payment_hash, payment_secret, callback_msg, |_|{}, callback_node, expected_retryable, expected_error_code, expected_channel_update); + run_onion_failure_test_with_fail_intercept(_name, test_case, nodes, route, payment_hash, payment_secret, callback_msg, |_|{}, callback_node, expected_retryable, expected_error_code, expected_channel_update, expected_short_channel_id); } // test_case @@ -54,7 +54,7 @@ fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, // 3: final node fails backward (but tamper onion payloads from node0) // 100: trigger error in the intermediate node and tamper returning fail_htlc // 200: trigger error in the final node and tamper returning fail_htlc -fn run_onion_failure_test_with_fail_intercept(_name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, mut callback_msg: F1, mut callback_fail: F2, mut callback_node: F3, expected_retryable: bool, expected_error_code: Option, expected_channel_update: Option) +fn run_onion_failure_test_with_fail_intercept(_name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, mut callback_msg: F1, mut callback_fail: F2, mut callback_node: F3, expected_retryable: bool, expected_error_code: Option, expected_channel_update: Option, expected_short_channel_id: Option) where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), F2: for <'a> FnMut(&'a mut msgs::UpdateFailHTLC), F3: FnMut(), @@ -163,7 +163,7 @@ fn run_onion_failure_test_with_fail_intercept(_name: &str, test_case: let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); - if let &Event::PaymentPathFailed { payment_hash:_, ref rejected_by_dest, ref network_update, ref error_code, error_data: _, ref all_paths_failed, path: _ } = &events[0] { + 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] { assert_eq!(*rejected_by_dest, !expected_retryable); assert_eq!(*all_paths_failed, true); assert_eq!(*error_code, expected_error_code); @@ -197,20 +197,28 @@ fn run_onion_failure_test_with_fail_intercept(_name: &str, test_case: } else { assert!(network_update.is_none()); } + if let Some(expected_short_channel_id) = expected_short_channel_id { + match short_channel_id { + Some(short_channel_id) => assert_eq!(*short_channel_id, expected_short_channel_id), + None => panic!("Expected short channel id"), + } + } else { + assert!(short_channel_id.is_none()); + } } else { panic!("Unexpected event"); } } impl msgs::ChannelUpdate { - fn dummy() -> msgs::ChannelUpdate { + fn dummy(short_channel_id: u64) -> msgs::ChannelUpdate { use bitcoin::secp256k1::ffi::Signature as FFISignature; use bitcoin::secp256k1::Signature; msgs::ChannelUpdate { signature: Signature::from(unsafe { FFISignature::new() }), contents: msgs::UnsignedChannelUpdate { chain_hash: BlockHash::hash(&vec![0u8][..]), - short_channel_id: 0, + short_channel_id, timestamp: 0, flags: 0, cltv_expiry_delta: 0, @@ -269,10 +277,13 @@ fn test_fee_failures() { pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], 40_000, payment_hash_success, payment_secret_success); claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage_success); + // If the hop gives fee_insufficient but enough fees were provided, then the previous hop + // malleated the payment before forwarding, taking funds when they shouldn't have. let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); + let short_channel_id = channels[0].0.contents.short_channel_id; run_onion_failure_test("fee_insufficient", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; - }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelClosed { short_channel_id: channels[0].0.contents.short_channel_id, is_permanent: true})); + }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelClosed { short_channel_id, is_permanent: true}), Some(short_channel_id)); // In an earlier version, we spuriously failed to forward payments if the expected feerate // changed between the channel open and the payment. @@ -320,6 +331,7 @@ fn test_onion_failure() { send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 40000); // intermediate node failure + let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test("invalid_realm", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let cur_height = nodes[0].best_block_info().1 + 1; @@ -333,9 +345,10 @@ fn test_onion_failure() { // describing a length-1 TLV payload, which is obviously bogus. new_payloads[0].data[0] = 1; msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash); - }, ||{}, true, Some(PERM|22), Some(NetworkUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true})); + }, ||{}, true, Some(PERM|22), Some(NetworkUpdate::ChannelClosed{short_channel_id, is_permanent: true}), Some(short_channel_id)); // final node failure + let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, &payment_secret, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let cur_height = nodes[0].best_block_info().1 + 1; @@ -349,7 +362,7 @@ fn test_onion_failure() { // length-1 TLV payload, which is obviously bogus. new_payloads[1].data[0] = 1; msg.onion_routing_packet = onion_utils::construct_onion_packet_bogus_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash); - }, ||{}, false, Some(PERM|22), Some(NetworkUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true})); + }, ||{}, false, Some(PERM|22), Some(NetworkUpdate::ChannelClosed{short_channel_id, is_permanent: true}), Some(short_channel_id)); // the following three with run_onion_failure_test_with_fail_intercept() test only the origin node // receiving simulated fail messages @@ -362,7 +375,7 @@ fn test_onion_failure() { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], NODE|2, &[0;0]); - }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: false})); + }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: false}), Some(route.paths[0][0].short_channel_id)); // final node failure run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -372,7 +385,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], NODE|2, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); - }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: false})); + }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: false}), Some(route.paths[0][1].short_channel_id)); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); // intermediate node failure @@ -382,7 +395,7 @@ fn test_onion_failure() { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|NODE|2, &[0;0]); - }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true})); + }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}), Some(route.paths[0][0].short_channel_id)); // final node failure run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -391,7 +404,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], PERM|NODE|2, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); - }, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true})); + }, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}), Some(route.paths[0][1].short_channel_id)); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); // intermediate node failure @@ -403,7 +416,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|NODE|3, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); - }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true})); + }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}), Some(route.paths[0][0].short_channel_id)); // final node failure run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -412,26 +425,31 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[1].shared_secret[..], PERM|NODE|3, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); - }, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true})); + }, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}), Some(route.paths[0][1].short_channel_id)); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); + // Our immediate peer sent UpdateFailMalformedHTLC because it couldn't understand the onion in + // the UpdateAddHTLC that we sent. + let short_channel_id = channels[0].0.contents.short_channel_id; run_onion_failure_test("invalid_onion_version", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.onion_routing_packet.version = 1; }, ||{}, true, - Some(BADONION|PERM|4), None); + Some(BADONION|PERM|4), None, Some(short_channel_id)); run_onion_failure_test("invalid_onion_hmac", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.onion_routing_packet.hmac = [3; 32]; }, ||{}, true, - Some(BADONION|PERM|5), None); + Some(BADONION|PERM|5), None, Some(short_channel_id)); run_onion_failure_test("invalid_onion_key", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.onion_routing_packet.public_key = Err(secp256k1::Error::InvalidPublicKey);}, ||{}, true, - Some(BADONION|PERM|6), None); + Some(BADONION|PERM|6), None, Some(short_channel_id)); + let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test_with_fail_intercept("temporary_channel_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); - msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], UPDATE|7, &ChannelUpdate::dummy().encode_with_len()[..]); - }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()})); + msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], UPDATE|7, &ChannelUpdate::dummy(short_channel_id).encode_with_len()[..]); + }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy(short_channel_id)}), Some(short_channel_id)); + let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test_with_fail_intercept("permanent_channel_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; }, |msg| { @@ -439,8 +457,9 @@ fn test_onion_failure() { let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|8, &[0;0]); // short_channel_id from the processing node - }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true})); + }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelClosed{short_channel_id, is_permanent: true}), Some(short_channel_id)); + let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test_with_fail_intercept("required_channel_feature_missing", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; }, |msg| { @@ -448,18 +467,20 @@ fn test_onion_failure() { let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); msg.reason = onion_utils::build_first_hop_failure_packet(&onion_keys[0].shared_secret[..], PERM|9, &[0;0]); // short_channel_id from the processing node - }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true})); + }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelClosed{short_channel_id, is_permanent: true}), Some(short_channel_id)); let mut bogus_route = route.clone(); bogus_route.paths[0][1].short_channel_id -= 1; + let short_channel_id = bogus_route.paths[0][1].short_channel_id; run_onion_failure_test("unknown_next_peer", 0, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(PERM|10), - Some(NetworkUpdate::ChannelClosed{short_channel_id: bogus_route.paths[0][1].short_channel_id, is_permanent:true})); + Some(NetworkUpdate::ChannelClosed{short_channel_id, is_permanent:true}), Some(short_channel_id)); + let short_channel_id = channels[1].0.contents.short_channel_id; let amt_to_forward = nodes[1].node.channel_state.lock().unwrap().by_id.get(&channels[1].2).unwrap().get_counterparty_htlc_minimum_msat() - 1; let mut bogus_route = route.clone(); let route_len = bogus_route.paths[0].len(); bogus_route.paths[0][route_len-1].fee_msat = amt_to_forward; - run_onion_failure_test("amount_below_minimum", 0, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(UPDATE|11), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()})); + run_onion_failure_test("amount_below_minimum", 0, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(UPDATE|11), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy(short_channel_id)}), Some(short_channel_id)); // Test a positive test-case with one extra msat, meeting the minimum. bogus_route.paths[0][route_len-1].fee_msat = amt_to_forward + 1; @@ -468,25 +489,28 @@ fn test_onion_failure() { //TODO: with new config API, we will be able to generate both valid and //invalid channel_update cases. + let short_channel_id = channels[0].0.contents.short_channel_id; run_onion_failure_test("fee_insufficient", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; - }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelClosed { short_channel_id: channels[0].0.contents.short_channel_id, is_permanent: true})); + }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelClosed { short_channel_id, is_permanent: true}), Some(short_channel_id)); + let short_channel_id = channels[0].0.contents.short_channel_id; run_onion_failure_test("incorrect_cltv_expiry", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { // need to violate: cltv_expiry - cltv_expiry_delta >= outgoing_cltv_value msg.cltv_expiry -= 1; - }, || {}, true, Some(UPDATE|13), Some(NetworkUpdate::ChannelClosed { short_channel_id: channels[0].0.contents.short_channel_id, is_permanent: true})); + }, || {}, true, Some(UPDATE|13), Some(NetworkUpdate::ChannelClosed { short_channel_id, is_permanent: true}), Some(short_channel_id)); + let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test("expiry_too_soon", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { let height = msg.cltv_expiry - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS + 1; connect_blocks(&nodes[0], height - nodes[0].best_block_info().1); connect_blocks(&nodes[1], height - nodes[1].best_block_info().1); connect_blocks(&nodes[2], height - nodes[2].best_block_info().1); - }, ||{}, true, Some(UPDATE|14), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()})); + }, ||{}, true, Some(UPDATE|14), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy(short_channel_id)}), Some(short_channel_id)); run_onion_failure_test("unknown_payment_hash", 2, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { nodes[2].node.fail_htlc_backwards(&payment_hash); - }, false, Some(PERM|15), None); + }, false, Some(PERM|15), None, None); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); run_onion_failure_test("final_expiry_too_soon", 1, &nodes, &route, &payment_hash, &payment_secret, |msg| { @@ -494,7 +518,7 @@ fn test_onion_failure() { connect_blocks(&nodes[0], height - nodes[0].best_block_info().1); connect_blocks(&nodes[1], height - nodes[1].best_block_info().1); connect_blocks(&nodes[2], height - nodes[2].best_block_info().1); - }, || {}, true, Some(17), None); + }, || {}, true, Some(17), None, None); run_onion_failure_test("final_incorrect_cltv_expiry", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { for (_, pending_forwards) in nodes[1].node.channel_state.lock().unwrap().forward_htlcs.iter_mut() { @@ -506,7 +530,7 @@ fn test_onion_failure() { } } } - }, true, Some(18), None); + }, true, Some(18), None, Some(channels[1].0.contents.short_channel_id)); run_onion_failure_test("final_incorrect_htlc_amount", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { // violate amt_to_forward > msg.amount_msat @@ -519,13 +543,14 @@ fn test_onion_failure() { } } } - }, true, Some(19), None); + }, true, Some(19), None, Some(channels[1].0.contents.short_channel_id)); + let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test("channel_disabled", 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { // disconnect event to the channel between nodes[1] ~ nodes[2] nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id(), false); nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false); - }, true, Some(UPDATE|20), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy()})); + }, true, Some(UPDATE|20), Some(NetworkUpdate::ChannelUpdateMessage{msg: ChannelUpdate::dummy(short_channel_id)}), Some(short_channel_id)); reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (false, false)); run_onion_failure_test("expiry_too_far", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { @@ -538,7 +563,5 @@ fn test_onion_failure() { let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash); msg.cltv_expiry = htlc_cltv; msg.onion_routing_packet = onion_packet; - }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true})); + }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0][0].pubkey, is_permanent: true}), Some(route.paths[0][0].short_channel_id)); } - - diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 37ed8c824..20ff0c834 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -329,9 +329,10 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type: /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an /// OutboundRoute). -/// Returns update, a boolean indicating that the payment itself failed, and the error code. +/// Returns update, a boolean indicating that the payment itself failed, the short channel id of +/// the responsible channel, and the error code. #[inline] -pub(super) fn process_onion_failure(secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec) -> (Option, bool, Option, Option>) where L::Target: Logger { +pub(super) fn process_onion_failure(secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec) -> (Option, Option, bool, Option, Option>) where L::Target: Logger { if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat, .. } = htlc_source { let mut res = None; let mut htlc_msat = *first_hop_htlc_msat; @@ -354,7 +355,10 @@ pub(super) fn process_onion_failure(secp_ctx: & 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.len(); + let failing_route_hop = if is_from_final_node { route_hop } else { &path[route_hop_idx + 1] }; if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { let um = gen_um_from_shared_secret(&shared_secret[..]); @@ -381,18 +385,21 @@ pub(super) fn process_onion_failure(secp_ctx: & } && is_from_final_node; // PERM bit observed below even if this error is from the intermediate nodes let mut network_update = None; + let mut short_channel_id = None; if error_code & NODE == NODE { - network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: error_code & PERM == PERM }); + let is_permanent = error_code & PERM == PERM; + network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent }); + short_channel_id = Some(route_hop.short_channel_id); } else if error_code & PERM == PERM { - network_update = if payment_failed { None } else { - let failing_route_hop = if is_from_final_node { route_hop } else { &path[route_hop_idx + 1] }; - Some(NetworkUpdate::ChannelClosed { + if !payment_failed { + network_update = Some(NetworkUpdate::ChannelClosed { short_channel_id: failing_route_hop.short_channel_id, is_permanent: true, - }) - }; + }); + short_channel_id = Some(failing_route_hop.short_channel_id); + } } else if error_code & UPDATE == UPDATE { if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) { @@ -415,15 +422,20 @@ pub(super) fn process_onion_failure(secp_ctx: & 20 => chan_update.contents.flags & 2 == 0, _ => false, // unknown error code; take channel_update as valid }; - network_update = if is_chan_update_invalid { + if is_chan_update_invalid { // This probably indicates the node which forwarded // to the node in question corrupted something. - Some(NetworkUpdate::ChannelClosed { + network_update = Some(NetworkUpdate::ChannelClosed { short_channel_id: route_hop.short_channel_id, is_permanent: true, - }) + }); } else { - Some(NetworkUpdate::ChannelUpdateMessage { + // Make sure the ChannelUpdate contains the expected + // short channel id. + if failing_route_hop.short_channel_id == chan_update.contents.short_channel_id { + short_channel_id = Some(failing_route_hop.short_channel_id); + } + network_update = Some(NetworkUpdate::ChannelUpdateMessage { msg: chan_update, }) }; @@ -438,7 +450,17 @@ pub(super) fn process_onion_failure(secp_ctx: & is_permanent: true, }); } - } else if !payment_failed { + if short_channel_id.is_none() { + short_channel_id = Some(route_hop.short_channel_id); + } + } else if payment_failed { + // Only blame the hop when a value in the HTLC doesn't match the + // corresponding value in the onion. + short_channel_id = match error_code & 0xff { + 18|19 => Some(route_hop.short_channel_id), + _ => None, + }; + } else { // We can't understand their error messages and they failed to // forward...they probably can't understand our forwards so its // really not worth trying any further. @@ -446,12 +468,13 @@ pub(super) fn process_onion_failure(secp_ctx: & node_id: route_hop.pubkey, is_permanent: true, }); + short_channel_id = Some(route_hop.short_channel_id); } // TODO: Here (and a few other places) we assume that BADONION errors // are always "sourced" from the node previous to the one which failed // to decode the onion. - res = Some((network_update, !(error_code & PERM == PERM && is_from_final_node))); + res = Some((network_update, short_channel_id, !(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 { @@ -463,20 +486,22 @@ pub(super) fn process_onion_failure(secp_ctx: & } else { // Useless packet that we can't use but it passed HMAC, so it // definitely came from the peer in question - res = Some((Some(NetworkUpdate::NodeFailure { + let network_update = Some(NetworkUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: true, - }), !is_from_final_node)); + }); + let short_channel_id = Some(route_hop.short_channel_id); + res = Some((network_update, short_channel_id, !is_from_final_node)); } } } }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?"); - if let Some((channel_update, payment_retryable)) = res { - (channel_update, payment_retryable, error_code_ret, error_packet_ret) + if let Some((channel_update, short_channel_id, payment_retryable)) = res { + (channel_update, short_channel_id, payment_retryable, error_code_ret, error_packet_ret) } else { // only not set either packet unparseable or hmac does not match with any // payment not retryable only when garbage is from the final node - (None, !is_from_final_node, None, None) + (None, None, !is_from_final_node, None, None) } } else { unreachable!(); } } diff --git a/lightning/src/routing/network_graph.rs b/lightning/src/routing/network_graph.rs index bdb305e78..19786947f 100644 --- a/lightning/src/routing/network_graph.rs +++ b/lightning/src/routing/network_graph.rs @@ -1745,6 +1745,7 @@ mod tests { network_update: Some(NetworkUpdate::ChannelUpdateMessage { msg: valid_channel_update, }), + short_channel_id: None, error_code: None, error_data: None, }); @@ -1770,6 +1771,7 @@ mod tests { short_channel_id, is_permanent: false, }), + short_channel_id: None, error_code: None, error_data: None, }); @@ -1793,6 +1795,7 @@ mod tests { short_channel_id, is_permanent: true, }), + short_channel_id: None, error_code: None, error_data: None, }); diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index 1319f7d21..fcc625db0 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -203,6 +203,11 @@ pub enum Event { all_paths_failed: bool, /// The payment path that failed. path: Vec, + /// The channel responsible for the failed payment path. + /// + /// If this is `Some`, then the corresponding channel should be avoided when the payment is + /// retried. May be `None` for older [`Event`] serializations. + short_channel_id: Option, #[cfg(test)] error_code: Option, #[cfg(test)] @@ -295,7 +300,7 @@ impl Writeable for Event { }); }, &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, - ref all_paths_failed, ref path, + ref all_paths_failed, ref path, ref short_channel_id, #[cfg(test)] ref error_code, #[cfg(test)] @@ -312,6 +317,7 @@ impl Writeable for Event { (2, rejected_by_dest, required), (3, all_paths_failed, required), (5, path, vec_type), + (7, short_channel_id, option), }); }, &Event::PendingHTLCsForwardable { time_forwardable: _ } => { @@ -409,12 +415,14 @@ impl MaybeReadable for Event { let mut network_update = None; let mut all_paths_failed = Some(true); let mut path: Option> = Some(vec![]); + let mut short_channel_id = None; read_tlv_fields!(reader, { (0, payment_hash, required), (1, network_update, ignorable), (2, rejected_by_dest, required), (3, all_paths_failed, option), (5, path, vec_type), + (7, short_channel_id, ignorable), }); Ok(Some(Event::PaymentPathFailed { payment_hash, @@ -422,6 +430,7 @@ impl MaybeReadable for Event { network_update, all_paths_failed: all_paths_failed.unwrap(), path: path.unwrap(), + short_channel_id, #[cfg(test)] error_code, #[cfg(test)]