mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 23:08:36 +01:00
Add failing short channel id to PaymentPathFailed
This will be useful for scoring channels when a payment fails.
This commit is contained in:
parent
1e2ca5233b
commit
202acd9e16
6 changed files with 124 additions and 59 deletions
|
@ -2888,6 +2888,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> 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)]
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -40,11 +40,11 @@ use core::default::Default;
|
|||
|
||||
use ln::functional_test_utils::*;
|
||||
|
||||
fn run_onion_failure_test<F1,F2>(_name: &str, test_case: u8, nodes: &Vec<Node>, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option<u16>, expected_channel_update: Option<NetworkUpdate>)
|
||||
fn run_onion_failure_test<F1,F2>(_name: &str, test_case: u8, nodes: &Vec<Node>, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option<u16>, expected_channel_update: Option<NetworkUpdate>, expected_short_channel_id: Option<u64>)
|
||||
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<F1,F2>(_name: &str, test_case: u8, nodes: &Vec<Node>,
|
|||
// 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<F1,F2,F3>(_name: &str, test_case: u8, nodes: &Vec<Node>, 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<u16>, expected_channel_update: Option<NetworkUpdate>)
|
||||
fn run_onion_failure_test_with_fail_intercept<F1,F2,F3>(_name: &str, test_case: u8, nodes: &Vec<Node>, 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<u16>, expected_channel_update: Option<NetworkUpdate>, expected_short_channel_id: Option<u64>)
|
||||
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<F1,F2,F3>(_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<F1,F2,F3>(_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));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<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 {
|
||||
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<u64>, bool, Option<u16>, Option<Vec<u8>>) 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<T: secp256k1::Signing, L: Deref>(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<T: secp256k1::Signing, L: Deref>(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<T: secp256k1::Signing, L: Deref>(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<T: secp256k1::Signing, L: Deref>(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<T: secp256k1::Signing, L: Deref>(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<T: secp256k1::Signing, L: Deref>(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!(); }
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -203,6 +203,11 @@ pub enum Event {
|
|||
all_paths_failed: bool,
|
||||
/// The payment path that failed.
|
||||
path: Vec<RouteHop>,
|
||||
/// 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<u64>,
|
||||
#[cfg(test)]
|
||||
error_code: Option<u16>,
|
||||
#[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<Vec<RouteHop>> = 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)]
|
||||
|
|
Loading…
Add table
Reference in a new issue