From 90a1405b647637c2dc3c55936b5abac03d467d2d Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 7 Mar 2024 15:12:27 -0800 Subject: [PATCH 01/23] Add expected_htlc_destination argument to run_onion_failure_test This argument will be asserted on in a future commit to ensure we obtain the intended `HTLCHandlingFailed::failed_next_destination` per HTLC failure. --- lightning/src/ln/onion_route_tests.rs | 72 ++++++++++++++------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index abd930a9a91..1736103def0 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -44,11 +44,11 @@ use bitcoin::hex::FromHex; use crate::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, expected_short_channel_id: 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, expected_htlc_destination: 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, expected_short_channel_id); + 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, expected_htlc_destination); } // test_case @@ -62,7 +62,8 @@ 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 + expected_channel_update: Option, expected_short_channel_id: Option, + expected_htlc_destination: Option, ) where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), F2: for <'a> FnMut(&'a mut msgs::UpdateFailHTLC), @@ -302,7 +303,7 @@ fn test_fee_failures() { let short_channel_id = channels[1].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::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id)); + }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), None); // In an earlier version, we spuriously failed to forward payments if the expected feerate // changed between the channel open and the payment. @@ -365,7 +366,7 @@ 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_with_writable_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - }, ||{}, true, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id)); + }, ||{}, true, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); // final node failure let short_channel_id = channels[1].0.contents.short_channel_id; @@ -384,7 +385,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_with_writable_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - }, ||{}, false, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id)); + }, ||{}, false, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); // the following three with run_onion_failure_test_with_fail_intercept() test only the origin node // receiving simulated fail messages @@ -397,7 +398,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.as_ref(), NODE|2, &[0;0]); - }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id)); + }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), None); // final node failure run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -407,7 +408,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); - }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id)); + }, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: false}), Some(route.paths[0].hops[1].short_channel_id), None); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); // intermediate node failure @@ -417,7 +418,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.as_ref(), PERM|NODE|2, &[0;0]); - }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id)); + }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), None); // final node failure run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -426,7 +427,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), 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].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id)); + }, false, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); // intermediate node failure @@ -438,7 +439,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), 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].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id)); + }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), None); // final node failure run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -447,20 +448,20 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), 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].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id)); + }, false, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[1].pubkey, is_permanent: true}), Some(route.paths[0].hops[1].short_channel_id), None); 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(short_channel_id)); + Some(BADONION|PERM|4), None, Some(short_channel_id), None); 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(short_channel_id)); + Some(BADONION|PERM|5), None, Some(short_channel_id), None); 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(short_channel_id)); + Some(BADONION|PERM|6), None, Some(short_channel_id), None); let short_channel_id = channels[1].0.contents.short_channel_id; let chan_update = ChannelUpdate::dummy(short_channel_id); @@ -477,7 +478,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data); }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id)); + Some(short_channel_id), None); // Check we can still handle onion failures that include channel updates without a type prefix let err_data_without_type = chan_update.encode_with_len(); @@ -489,7 +490,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type); }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id)); + Some(short_channel_id), None); 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| { @@ -499,7 +500,7 @@ 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.as_ref(), PERM|8, &[0;0]); // short_channel_id from the processing node - }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id)); + }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); 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| { @@ -509,13 +510,13 @@ 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.as_ref(), PERM|9, &[0;0]); // short_channel_id from the processing node - }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id)); + }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); let mut bogus_route = route.clone(); bogus_route.paths[0].hops[1].short_channel_id -= 1; let short_channel_id = bogus_route.paths[0].hops[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::ChannelFailure{short_channel_id, is_permanent:true}), Some(short_channel_id)); + Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent:true}), Some(short_channel_id), None); let short_channel_id = channels[1].0.contents.short_channel_id; let amt_to_forward = nodes[1].node.per_peer_state.read().unwrap().get(&nodes[2].node.get_our_node_id()) @@ -526,7 +527,7 @@ fn test_onion_failure() { bogus_route.paths[0].hops[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::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id)); + Some(short_channel_id), None); // Clear pending payments so that the following positive test has the correct payment hash. for node in nodes.iter() { @@ -543,13 +544,13 @@ fn test_onion_failure() { let short_channel_id = channels[1].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::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id)); + }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), None); let short_channel_id = channels[1].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::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id)); + }, || {}, true, Some(UPDATE|13), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), None); 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| { @@ -559,11 +560,11 @@ fn test_onion_failure() { connect_blocks(&nodes[2], height - nodes[2].best_block_info().1); }, ||{}, true, Some(UPDATE|14), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id)); + Some(short_channel_id), None); 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, None); + }, false, Some(PERM|15), None, 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| { @@ -571,7 +572,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); - }, || {}, false, Some(0x4000 | 15), None, None); + }, || {}, false, Some(0x4000 | 15), None, None, None); run_onion_failure_test("final_incorrect_cltv_expiry", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { @@ -583,7 +584,7 @@ fn test_onion_failure() { } } } - }, true, Some(18), None, Some(channels[1].0.contents.short_channel_id)); + }, true, Some(18), None, Some(channels[1].0.contents.short_channel_id), None); run_onion_failure_test("final_incorrect_htlc_amount", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { // violate amt_to_forward > msg.amount_msat @@ -596,7 +597,7 @@ fn test_onion_failure() { } } } - }, true, Some(19), None, Some(channels[1].0.contents.short_channel_id)); + }, true, Some(19), None, Some(channels[1].0.contents.short_channel_id), None); let short_channel_id = channels[1].0.contents.short_channel_id; run_onion_failure_test("channel_disabled", 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { @@ -605,7 +606,7 @@ fn test_onion_failure() { nodes[2].node.peer_disconnected(nodes[1].node.get_our_node_id()); }, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id)); + Some(short_channel_id), None); run_onion_failure_test("channel_disabled", 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { // disconnect event to the channel between nodes[1] ~ nodes[2] for _ in 0..DISABLE_GOSSIP_TICKS + 1 { @@ -616,7 +617,7 @@ fn test_onion_failure() { nodes[2].node.get_and_clear_pending_msg_events(); }, true, Some(UPDATE|20), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id)); + Some(short_channel_id), None); reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[2])); run_onion_failure_test("expiry_too_far", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { @@ -631,7 +632,7 @@ fn test_onion_failure() { let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); msg.cltv_expiry = htlc_cltv; msg.onion_routing_packet = onion_packet; - }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id)); + }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), None); run_onion_failure_test_with_fail_intercept("mpp_timeout", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { // Tamper returning error message @@ -640,7 +641,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0]); }, ||{ nodes[2].node.fail_htlc_backwards(&payment_hash); - }, true, Some(23), None, None); + }, true, Some(23), None, None, None); run_onion_failure_test_with_fail_intercept("bogus err packet with valid hmac", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -659,7 +660,7 @@ fn test_onion_failure() { &onion_keys[1].shared_secret.as_ref(), &decoded_err_packet.encode()[..]) }, || nodes[2].node.fail_htlc_backwards(&payment_hash), false, None, Some(NetworkUpdate::NodeFailure { node_id: route.paths[0].hops[1].pubkey, is_permanent: true }), - Some(channels[1].0.contents.short_channel_id)); + Some(channels[1].0.contents.short_channel_id), None); run_onion_failure_test_with_fail_intercept("0-length channel update in intermediate node UPDATE onion failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; @@ -685,7 +686,7 @@ fn test_onion_failure() { short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: false, }), - Some(channels[1].0.contents.short_channel_id)); + Some(channels[1].0.contents.short_channel_id), None); run_onion_failure_test_with_fail_intercept("0-length channel update in final node UPDATE onion failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); @@ -709,7 +710,7 @@ fn test_onion_failure() { short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: false, }), - Some(channels[1].0.contents.short_channel_id)); + Some(channels[1].0.contents.short_channel_id), None); } #[test] @@ -861,6 +862,7 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { run_onion_failure_test( name, 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {}, true, Some(error_code), Some(network_update), Some(short_channel_id), + None, ); }; From 2734012c3591ffec960db833744ad200ec2b83dc Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 8 Mar 2024 01:43:07 -0800 Subject: [PATCH 02/23] Enable decoding new incoming HTLC onions when fully committed This commit ensures all new incoming HTLCs going forward will have their onion decoded when they become fully committed to decide how we should proceed with each one. As a result, we'll obtain `HTLCHandlingFailed` events for _any_ failed HTLC that comes across a channel. Previously, we would evaluate the incoming HTLC within `can_accept_incoming_htlc` upon receiving it, but not yet committed, so we'd always have to account for it ourselves manually when checking certain HTLC limits. With this change, we no longer need to do so as it will already be accounted for within the pending HTLC stats computation. We will now start writing channels with the new serialization version (4), and we will still be able to downgrade back to the commit that introduced it since reading version 4 is supported. Note that existing pending inbound HTLCs may already have their resolution if they were received in a previous version of LDK. We must support those until we no longer allow downgrading beyond this commit. --- fuzz/src/full_stack.rs | 24 +-- lightning/src/events/mod.rs | 2 + lightning/src/ln/blinded_payment_tests.rs | 68 ++++++-- lightning/src/ln/channel.rs | 96 ++++------- lightning/src/ln/channelmanager.rs | 84 +--------- lightning/src/ln/functional_test_utils.rs | 17 +- lightning/src/ln/functional_tests.rs | 24 ++- lightning/src/ln/onion_route_tests.rs | 121 ++++++++------ lightning/src/ln/payment_tests.rs | 190 +++++++++++++--------- lightning/src/ln/priv_short_conf_tests.rs | 19 +++ lightning/src/ln/reload_tests.rs | 9 +- lightning/src/ln/shutdown_tests.rs | 6 + 12 files changed, 359 insertions(+), 301 deletions(-) diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index c1f2dd11b1e..1b3e576da6c 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -1338,8 +1338,8 @@ mod tests { // end of update_add_htlc from 0 to 1 via client and mac ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); - // Two feerate requests to check dust exposure - ext_from_hex("00fd00fd", &mut test); + // One feerate request to check dust exposure + ext_from_hex("00fd", &mut test); // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); @@ -1362,8 +1362,8 @@ mod tests { // process the now-pending HTLC forward ext_from_hex("07", &mut test); - // Three feerate requests to check dust exposure - ext_from_hex("00fd00fd00fd", &mut test); + // Four feerate requests to check dust exposure while forwarding the HTLC + ext_from_hex("00fd00fd00fd00fd", &mut test); // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7: UpdateHTLCs event for node 03020000 with 1 HTLCs for channel 3f000000) // we respond with commitment_signed then revoke_and_ack (a weird, but valid, order) @@ -1439,8 +1439,8 @@ mod tests { // end of update_add_htlc from 0 to 1 via client and mac ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ab00000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); - // Two feerate requests to check dust exposure - ext_from_hex("00fd00fd", &mut test); + // One feerate request to check dust exposure + ext_from_hex("00fd", &mut test); // now respond to the update_fulfill_htlc+commitment_signed messages the client sent to peer 0 // inbound read from peer id 0 of len 18 @@ -1474,8 +1474,8 @@ mod tests { // process the now-pending HTLC forward ext_from_hex("07", &mut test); - // Three feerate requests to check dust exposure - ext_from_hex("00fd00fd00fd", &mut test); + // Four feerate requests to check dust exposure while forwarding the HTLC + ext_from_hex("00fd00fd00fd00fd", &mut test); // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate) // we respond with revoke_and_ack, then commitment_signed, then update_fail_htlc @@ -1574,8 +1574,8 @@ mod tests { // end of update_add_htlc from 0 to 1 via client and mac ext_from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 5300000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test); - // Two feerate requests to check dust exposure - ext_from_hex("00fd00fd", &mut test); + // One feerate request to check dust exposure + ext_from_hex("00fd", &mut test); // inbound read from peer id 0 of len 18 ext_from_hex("030012", &mut test); @@ -1598,8 +1598,8 @@ mod tests { // process the now-pending HTLC forward ext_from_hex("07", &mut test); - // Three feerate requests to check dust exposure - ext_from_hex("00fd00fd00fd", &mut test); + // Four feerate requests to check dust exposure while forwarding the HTLC + ext_from_hex("00fd00fd00fd00fd", &mut test); // client now sends id 1 update_add_htlc and commitment_signed (CHECK 7 duplicate) // connect a block with one transaction of len 125 diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 5bc446f9724..c5572822643 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1432,6 +1432,8 @@ pub enum Event { /// * When an unknown SCID is requested for forwarding a payment. /// * Expected MPP amount has already been reached /// * The HTLC has timed out + /// * The HTLC failed to meet the forwarding requirements (i.e. insufficient fees paid, or a + /// CLTV that is too soon) /// /// This event, however, does not get generated if an HTLC fails to meet the forwarding /// requirements (i.e. insufficient fees paid, or a CLTV that is too soon). diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index c1fad65c14f..d27a7c8a235 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -309,8 +309,10 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { // We need the session priv to construct a bogus onion packet later. *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([3; 32]); create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; - let chan_upd_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0).0.contents; + let chan_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + let chan_upd_1_2 = chan_1_2.0.contents; + let chan_2_3 = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + let chan_upd_2_3 = chan_2_3.0.contents; let amt_msat = 5000; let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); @@ -364,18 +366,27 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { check_added_monitors!(nodes[1], 0); do_commitment_signed_dance(&nodes[1], &nodes[0], &updates_0_1.commitment_signed, true, true); + expect_pending_htlcs_forwardable!(nodes[1]); + check_added_monitors!(nodes[1], 1); + if intro_fails { let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + let failed_destination = match check { + ForwardCheckFail::InboundOnionCheck => HTLCDestination::InvalidOnion, + ForwardCheckFail::ForwardPayloadEncodedAsReceive => HTLCDestination::FailedPayment { payment_hash }, + ForwardCheckFail::OutboundChannelCheck => + HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_1_2.2 }, + }; + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), &[failed_destination.clone()] + ); expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); return } - expect_pending_htlcs_forwardable!(nodes[1]); - check_added_monitors!(nodes[1], 1); - let mut updates_1_2 = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id()); let mut update_add = &mut updates_1_2.update_add_htlcs[0]; @@ -385,6 +396,17 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { check_added_monitors!(nodes[2], 0); do_commitment_signed_dance(&nodes[2], &nodes[1], &updates_1_2.commitment_signed, true, true); + expect_pending_htlcs_forwardable!(nodes[2]); + let failed_destination = match check { + ForwardCheckFail::InboundOnionCheck|ForwardCheckFail::ForwardPayloadEncodedAsReceive => HTLCDestination::InvalidOnion, + ForwardCheckFail::OutboundChannelCheck => + HTLCDestination::NextHopChannel { node_id: Some(nodes[3].node.get_our_node_id()), channel_id: chan_2_3.2 }, + }; + expect_htlc_handling_failed_destinations!( + nodes[2].node.get_and_clear_pending_events(), &[failed_destination.clone()] + ); + check_added_monitors!(nodes[2], 1); + let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); let update_malformed = &mut updates.update_fail_malformed_htlcs[0]; assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); @@ -444,7 +466,10 @@ fn failed_backwards_to_intro_node() { nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); check_added_monitors!(nodes[2], 0); do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event.commitment_msg, true, true); - nodes[2].node.process_pending_htlc_forwards(); + + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::InvalidOnion]); + check_added_monitors(&nodes[2], 1); let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); let mut update_malformed = &mut updates.update_fail_malformed_htlcs[0]; @@ -521,7 +546,7 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, // intro node will error backwards. $curr_node.node.peer_disconnected($next_node.node.get_our_node_id()); expect_pending_htlcs_forwardable!($curr_node); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node, + expect_htlc_handling_failed_destinations!($curr_node.node.get_and_clear_pending_events(), vec![HTLCDestination::NextHopChannel { node_id: Some($next_node.node.get_our_node_id()), channel_id: $failed_chan_id }]); }, ProcessPendingHTLCsCheck::FwdChannelClosed => { @@ -537,12 +562,12 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, crate::events::Event::ChannelClosed { .. } => {}, _ => panic!("Unexpected event {:?}", events), } + check_closed_broadcast(&$curr_node, 1, true); + check_added_monitors!($curr_node, 1); $curr_node.node.process_pending_htlc_forwards(); - expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!($curr_node, + expect_htlc_handling_failed_destinations!($curr_node.node.get_and_clear_pending_events(), vec![HTLCDestination::UnknownNextHop { requested_forward_scid: $failed_scid }]); - check_closed_broadcast(&$curr_node, 1, true); - check_added_monitors!($curr_node, 1); $curr_node.node.process_pending_htlc_forwards(); }, } @@ -628,6 +653,7 @@ fn do_blinded_intercept_payment(intercept_node_fails: bool) { }; nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], &payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); @@ -933,6 +959,9 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), update_add); check_added_monitors!(nodes[2], 0); do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::InvalidOnion]); + check_added_monitors(&nodes[2], 1); }, ReceiveCheckFail::ReceiveRequirements => { let update_add = &mut payment_event_1_2.msgs[0]; @@ -940,6 +969,9 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), update_add); check_added_monitors!(nodes[2], 0); do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]); + check_added_monitors(&nodes[2], 1); }, ReceiveCheckFail::ChannelCheck => { nodes[2].node.close_channel(&chan_id_1_2, &nodes[1].node.get_our_node_id()).unwrap(); @@ -953,6 +985,9 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { nodes[2].node.handle_shutdown(nodes[1].node.get_our_node_id(), &node_1_shutdown); commitment_signed_dance!(nodes[2], nodes[1], (), false, true, false, false); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]); + check_added_monitors(&nodes[2], 1); }, ReceiveCheckFail::ProcessPendingHTLCsCheck => { assert_eq!(payment_event_1_2.msgs[0].cltv_expiry, nodes[0].best_block_info().1 + 1 + excess_final_cltv_delta_opt.unwrap() as u32 + TEST_FINAL_CLTV); @@ -968,6 +1003,9 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event_1_2.msgs[0]); check_added_monitors!(nodes[2], 0); do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_1_2.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]); + check_added_monitors(&nodes[2], 1); } } @@ -1168,6 +1206,12 @@ fn min_htlc() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event_0_1.msgs[0]); check_added_monitors!(nodes[1], 0); do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event_0_1.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_1_2.2 }] + ); + check_added_monitors(&nodes[1], 1); let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); @@ -1497,9 +1541,11 @@ fn fails_receive_tlvs_authentication() { let mut payment_event = SendEvent::from_event(ev); nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); - check_added_monitors!(nodes[1], 0); do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[1]); nodes[1].node.process_pending_htlc_forwards(); + check_added_monitors!(nodes[1], 1); + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[HTLCDestination::InvalidOnion]); let mut update_fail = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(update_fail.update_fail_htlcs.len() == 1); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 78a8f5c6d02..3ed60bd8fd7 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3492,7 +3492,7 @@ impl ChannelContext where SP::Target: SignerProvider { } let htlc_above_dust = HTLCCandidate::new(real_dust_limit_success_sat * 1000, HTLCInitiator::LocalOffered); - let max_reserved_commit_tx_fee_msat = context.next_remote_commit_tx_fee_msat(htlc_above_dust, None); + let max_reserved_commit_tx_fee_msat = context.next_remote_commit_tx_fee_msat(Some(htlc_above_dust), None); let holder_selected_chan_reserve_msat = context.holder_selected_channel_reserve_satoshis * 1000; let remote_balance_msat = (context.channel_value_satoshis * 1000 - context.value_to_self_msat) @@ -3698,7 +3698,9 @@ impl ChannelContext where SP::Target: SignerProvider { /// second allows for creating a buffer to ensure a further HTLC can always be accepted/added. /// /// Dust HTLCs are excluded. - fn next_remote_commit_tx_fee_msat(&self, htlc: HTLCCandidate, fee_spike_buffer_htlc: Option<()>) -> u64 { + fn next_remote_commit_tx_fee_msat(&self, htlc: Option, fee_spike_buffer_htlc: Option<()>) -> u64 { + debug_assert!(htlc.is_some() || fee_spike_buffer_htlc.is_some(), "At least one of the options must be set"); + let context = &self; assert!(!context.is_outbound()); @@ -3713,15 +3715,17 @@ impl ChannelContext where SP::Target: SignerProvider { let mut addl_htlcs = 0; if fee_spike_buffer_htlc.is_some() { addl_htlcs += 1; } - match htlc.origin { - HTLCInitiator::LocalOffered => { - if htlc.amount_msat / 1000 >= real_dust_limit_success_sat { - addl_htlcs += 1; - } - }, - HTLCInitiator::RemoteOffered => { - if htlc.amount_msat / 1000 >= real_dust_limit_timeout_sat { - addl_htlcs += 1; + if let Some(htlc) = &htlc { + match htlc.origin { + HTLCInitiator::LocalOffered => { + if htlc.amount_msat / 1000 >= real_dust_limit_success_sat { + addl_htlcs += 1; + } + }, + HTLCInitiator::RemoteOffered => { + if htlc.amount_msat / 1000 >= real_dust_limit_timeout_sat { + addl_htlcs += 1; + } } } } @@ -3754,7 +3758,7 @@ impl ChannelContext where SP::Target: SignerProvider { let num_htlcs = included_htlcs + addl_htlcs; let res = commit_tx_fee_sat(context.feerate_per_kw, num_htlcs, &context.channel_type) * 1000; #[cfg(any(test, fuzzing))] - { + if let Some(htlc) = &htlc { let mut fee = res; if fee_spike_buffer_htlc.is_some() { fee = commit_tx_fee_sat(context.feerate_per_kw, num_htlcs - 1, &context.channel_type) * 1000; @@ -4770,8 +4774,7 @@ impl Channel where } pub fn update_add_htlc( - &mut self, msg: &msgs::UpdateAddHTLC, pending_forward_status: PendingHTLCStatus, - fee_estimator: &LowerBoundedFeeEstimator, + &mut self, msg: &msgs::UpdateAddHTLC, fee_estimator: &LowerBoundedFeeEstimator, ) -> Result<(), ChannelError> where F::Target: FeeEstimator { if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) { return Err(ChannelError::close("Got add HTLC message when channel was not in an operational state".to_owned())); @@ -4836,7 +4839,7 @@ impl Channel where { let remote_commit_tx_fee_msat = if self.context.is_outbound() { 0 } else { let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - self.context.next_remote_commit_tx_fee_msat(htlc_candidate, None) // Don't include the extra fee spike buffer HTLC in calculations + self.context.next_remote_commit_tx_fee_msat(Some(htlc_candidate), None) // Don't include the extra fee spike buffer HTLC in calculations }; let anchor_outputs_value_msat = if !self.context.is_outbound() && self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000 @@ -4871,12 +4874,6 @@ impl Channel where return Err(ChannelError::close("Remote provided CLTV expiry in seconds instead of block height".to_owned())); } - if self.context.channel_state.is_local_shutdown_sent() { - if let PendingHTLCStatus::Forward(_) = pending_forward_status { - panic!("ChannelManager shouldn't be trying to add a forwardable HTLC after we've started closing"); - } - } - // Now update local state: self.context.next_counterparty_htlc_id += 1; self.context.pending_inbound_htlcs.push(InboundHTLCOutput { @@ -4884,8 +4881,8 @@ impl Channel where amount_msat: msg.amount_msat, payment_hash: msg.payment_hash, cltv_expiry: msg.cltv_expiry, - state: InboundHTLCState::RemoteAnnounced(InboundHTLCResolution::Resolved { - pending_htlc_status: pending_forward_status + state: InboundHTLCState::RemoteAnnounced(InboundHTLCResolution::Pending { + update_add_htlc: msg.clone(), }), }); Ok(()) @@ -6995,7 +6992,7 @@ impl Channel where }; let exposure_dust_limit_timeout_sats = htlc_timeout_dust_limit + self.context.counterparty_dust_limit_satoshis; if msg.amount_msat / 1000 < exposure_dust_limit_timeout_sats { - let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat + msg.amount_msat; + let on_counterparty_tx_dust_htlc_exposure_msat = htlc_stats.on_counterparty_tx_dust_exposure_msat; if on_counterparty_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat { log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on counterparty commitment tx", on_counterparty_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); @@ -7015,7 +7012,7 @@ impl Channel where let exposure_dust_limit_success_sats = htlc_success_dust_limit + self.context.holder_dust_limit_satoshis; if msg.amount_msat / 1000 < exposure_dust_limit_success_sats { - let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat + msg.amount_msat; + let on_holder_tx_dust_htlc_exposure_msat = htlc_stats.on_holder_tx_dust_exposure_msat; if on_holder_tx_dust_htlc_exposure_msat > max_dust_htlc_exposure_msat { log_info!(logger, "Cannot accept value that would put our exposure to dust HTLCs at {} over the limit {} on holder commitment tx", on_holder_tx_dust_htlc_exposure_msat, max_dust_htlc_exposure_msat); @@ -7048,12 +7045,14 @@ impl Channel where // the spec because the fee spike buffer requirement doesn't exist on the receiver's // side, only on the sender's. Note that with anchor outputs we are no longer as // sensitive to fee spikes, so we need to account for them. - let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered); - let mut remote_fee_cost_incl_stuck_buffer_msat = self.context.next_remote_commit_tx_fee_msat(htlc_candidate, Some(())); + // + // A `None` `HTLCCandidate` is used as in this case because we're already accounting for + // the incoming HTLC as it has been fully committed by both sides. + let mut remote_fee_cost_incl_stuck_buffer_msat = self.context.next_remote_commit_tx_fee_msat(None, Some(())); if !self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() { remote_fee_cost_incl_stuck_buffer_msat *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; } - if pending_remote_value_msat.saturating_sub(msg.amount_msat).saturating_sub(self.context.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat { + if pending_remote_value_msat.saturating_sub(self.context.holder_selected_channel_reserve_satoshis * 1000).saturating_sub(anchor_outputs_value_msat) < remote_fee_cost_incl_stuck_buffer_msat { log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.context.channel_id()); return Err(("Fee spike buffer violation", 0x1000|7)); } @@ -9131,7 +9130,7 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) } const SERIALIZATION_VERSION: u8 = 4; -const MIN_SERIALIZATION_VERSION: u8 = 3; +const MIN_SERIALIZATION_VERSION: u8 = 4; impl_writeable_tlv_based_enum_legacy!(InboundHTLCRemovalReason,; (0, FailRelay), @@ -9192,18 +9191,7 @@ impl Writeable for Channel where SP::Target: SignerProvider { // Note that we write out as if remove_uncommitted_htlcs_and_mark_paused had just been // called. - let version_to_write = if self.context.pending_inbound_htlcs.iter().any(|htlc| match htlc.state { - InboundHTLCState::AwaitingRemoteRevokeToAnnounce(ref htlc_resolution)| - InboundHTLCState::AwaitingAnnouncedRemoteRevoke(ref htlc_resolution) => { - matches!(htlc_resolution, InboundHTLCResolution::Pending { .. }) - }, - _ => false, - }) { - SERIALIZATION_VERSION - } else { - MIN_SERIALIZATION_VERSION - }; - write_ver_prefix!(writer, version_to_write, MIN_SERIALIZATION_VERSION); + write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); // `user_id` used to be a single u64 value. In order to remain backwards compatible with // versions prior to 0.0.113, the u128 is serialized as two separate u64 values. We write @@ -9261,27 +9249,11 @@ impl Writeable for Channel where SP::Target: SignerProvider { &InboundHTLCState::RemoteAnnounced(_) => unreachable!(), &InboundHTLCState::AwaitingRemoteRevokeToAnnounce(ref htlc_resolution) => { 1u8.write(writer)?; - if version_to_write <= 3 { - if let InboundHTLCResolution::Resolved { pending_htlc_status } = htlc_resolution { - pending_htlc_status.write(writer)?; - } else { - panic!(); - } - } else { - htlc_resolution.write(writer)?; - } + htlc_resolution.write(writer)?; }, &InboundHTLCState::AwaitingAnnouncedRemoteRevoke(ref htlc_resolution) => { 2u8.write(writer)?; - if version_to_write <= 3 { - if let InboundHTLCResolution::Resolved { pending_htlc_status } = htlc_resolution { - pending_htlc_status.write(writer)?; - } else { - panic!(); - } - } else { - htlc_resolution.write(writer)?; - } + htlc_resolution.write(writer)?; }, &InboundHTLCState::Committed => { 3u8.write(writer)?; @@ -10429,7 +10401,7 @@ mod tests { node_a_chan.context.channel_transaction_parameters.is_outbound_from_holder = false; let remote_commit_fee_3_htlcs = commit_tx_fee_sat(node_a_chan.context.feerate_per_kw, 3, node_a_chan.context.get_channel_type()) * 1000; let htlc_candidate = HTLCCandidate::new(htlc_amount_msat, HTLCInitiator::LocalOffered); - let remote_commit_tx_fee = node_a_chan.context.next_remote_commit_tx_fee_msat(htlc_candidate, None); + let remote_commit_tx_fee = node_a_chan.context.next_remote_commit_tx_fee_msat(Some(htlc_candidate), None); assert_eq!(remote_commit_tx_fee, remote_commit_fee_3_htlcs); } @@ -10471,13 +10443,13 @@ mod tests { // If swapped: this HTLC would be counted as non-dust when it shouldn't be. let dust_htlc_amt_above_timeout = ((253 * htlc_timeout_tx_weight(chan.context.get_channel_type()) / 1000) + chan.context.counterparty_dust_limit_satoshis + 1) * 1000; let htlc_candidate = HTLCCandidate::new(dust_htlc_amt_above_timeout, HTLCInitiator::LocalOffered); - let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(htlc_candidate, None); + let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(Some(htlc_candidate), None); assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs); // If swapped: this HTLC would be counted as dust when it shouldn't be. let htlc_amt_below_success = ((253 * htlc_success_tx_weight(chan.context.get_channel_type()) / 1000) + chan.context.counterparty_dust_limit_satoshis - 1) * 1000; let htlc_candidate = HTLCCandidate::new(htlc_amt_below_success, HTLCInitiator::RemoteOffered); - let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(htlc_candidate, None); + let commitment_tx_fee = chan.context.next_remote_commit_tx_fee_msat(Some(htlc_candidate), None); assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc); } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 21ab790bf9c..314dbad5a3b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4359,34 +4359,6 @@ where }) } - fn decode_update_add_htlc_onion( - &self, msg: &msgs::UpdateAddHTLC, counterparty_node_id: &PublicKey, - ) -> Result< - (onion_utils::Hop, [u8; 32], Option>), HTLCFailureMsg - > { - let (next_hop, shared_secret, next_packet_details_opt) = decode_incoming_update_add_htlc_onion( - msg, &*self.node_signer, &*self.logger, &self.secp_ctx - )?; - - let next_packet_details = match next_packet_details_opt { - Some(next_packet_details) => next_packet_details, - // it is a receive, so no need for outbound checks - None => return Ok((next_hop, shared_secret, None)), - }; - - // Perform outbound checks here instead of in [`Self::construct_pending_htlc_info`] because we - // can't hold the outbound peer state lock at the same time as the inbound peer state lock. - self.can_forward_htlc(&msg, &next_packet_details).map_err(|e| { - let (err_msg, err_code) = e; - self.htlc_failure_from_update_add_err( - msg, counterparty_node_id, err_msg, err_code, - next_hop.is_intro_node_blinded_forward(), &shared_secret - ) - })?; - - Ok((next_hop, shared_secret, Some(next_packet_details.next_packet_pubkey))) - } - fn construct_pending_htlc_status<'a>( &self, msg: &msgs::UpdateAddHTLC, counterparty_node_id: &PublicKey, shared_secret: [u8; 32], decoded_hop: onion_utils::Hop, allow_underpay: bool, @@ -5593,7 +5565,7 @@ where Ok(()) } - fn process_pending_update_add_htlcs(&self) { + pub(crate) fn process_pending_update_add_htlcs(&self) { let mut decode_update_add_htlcs = new_hash_map(); mem::swap(&mut decode_update_add_htlcs, &mut self.decode_update_add_htlcs.lock().unwrap()); @@ -8663,7 +8635,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ // Note that the ChannelManager is NOT re-persisted on disk after this (unless we error // closing a channel), so any changes are likely to be lost on restart! - let decoded_hop_res = self.decode_update_add_htlc_onion(msg, counterparty_node_id); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -8675,53 +8646,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { - let mut pending_forward_info = match decoded_hop_res { - Ok((next_hop, shared_secret, next_packet_pk_opt)) => - self.construct_pending_htlc_status( - msg, counterparty_node_id, shared_secret, next_hop, - chan.context.config().accept_underpaying_htlcs, next_packet_pk_opt, - ), - Err(e) => PendingHTLCStatus::Fail(e) - }; - let logger = WithChannelContext::from(&self.logger, &chan.context, Some(msg.payment_hash)); - // If the update_add is completely bogus, the call will Err and we will close, - // but if we've sent a shutdown and they haven't acknowledged it yet, we just - // want to reject the new HTLC and fail it backwards instead of forwarding. - if let Err((_, error_code)) = chan.can_accept_incoming_htlc(&msg, &self.fee_estimator, &logger) { - if msg.blinding_point.is_some() { - pending_forward_info = PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed( - msgs::UpdateFailMalformedHTLC { - channel_id: msg.channel_id, - htlc_id: msg.htlc_id, - sha256_of_onion: [0; 32], - failure_code: INVALID_ONION_BLINDING, - } - )) - } else { - match pending_forward_info { - PendingHTLCStatus::Forward(PendingHTLCInfo { - ref incoming_shared_secret, ref routing, .. - }) => { - let reason = if routing.blinded_failure().is_some() { - HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]) - } else if (error_code & 0x1000) != 0 { - let error_data = self.get_htlc_inbound_temp_fail_data(error_code); - HTLCFailReason::reason(error_code, error_data) - } else { - HTLCFailReason::from_failure_code(error_code) - }.get_encrypted_failure_packet(incoming_shared_secret, &None); - let msg = msgs::UpdateFailHTLC { - channel_id: msg.channel_id, - htlc_id: msg.htlc_id, - reason - }; - pending_forward_info = PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msg)); - }, - _ => {}, - } - } - } - try_chan_phase_entry!(self, peer_state, chan.update_add_htlc(&msg, pending_forward_info, &self.fee_estimator), chan_phase_entry); + try_chan_phase_entry!(self, peer_state, chan.update_add_htlc(&msg, &self.fee_estimator), chan_phase_entry); } else { return try_chan_phase_entry!(self, peer_state, Err(ChannelError::close( "Got an update_add_htlc message for an unfunded channel!".into())), chan_phase_entry); @@ -14748,6 +14673,11 @@ mod tests { assert!(updates.update_fail_malformed_htlcs.is_empty()); assert!(updates.update_fee.is_none()); nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]); + commitment_signed_dance!(nodes[1], nodes[0], &updates.commitment_signed, false); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash: mismatch_payment_hash }]); + check_added_monitors(&nodes[1], 1); + let _ = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[1].logger.assert_log_contains("lightning::ln::channelmanager", "Payment preimage didn't match payment hash", 1); } diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index b4f172b4a27..5a5f0c78029 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1941,15 +1941,18 @@ macro_rules! expect_pending_htlcs_forwardable_conditions { #[macro_export] macro_rules! expect_htlc_handling_failed_destinations { ($events: expr, $expected_failures: expr) => {{ + let mut num_expected_failures = $expected_failures.len(); for event in $events { match event { $crate::events::Event::PendingHTLCsForwardable { .. } => { }, $crate::events::Event::HTLCHandlingFailed { ref failed_next_destination, .. } => { - assert!($expected_failures.contains(&failed_next_destination)) + assert!($expected_failures.contains(&failed_next_destination)); + num_expected_failures -= 1; }, _ => panic!("Unexpected destination"), } } + assert_eq!(num_expected_failures, 0); }} } @@ -2718,6 +2721,8 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option if is_last_hop && is_probe { commitment_signed_dance!(node, prev_node, payment_event.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(node); + check_added_monitors(node, 1); } else { commitment_signed_dance!(node, prev_node, payment_event.commitment_msg, false); expect_pending_htlcs_forwardable!(node); @@ -2801,22 +2806,26 @@ pub fn pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path do_pass_along_path(args) } -pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&[&Node<'a, 'b, 'c>]]) { +pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[(&[&Node<'a, 'b, 'c>], PaymentHash)]) { let mut events = origin_node.node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), expected_route.len()); check_added_monitors!(origin_node, expected_route.len()); - for path in expected_route.iter() { + for (path, payment_hash) in expected_route.iter() { let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events); - do_pass_along_path(PassAlongPathArgs::new(origin_node, path, 0, PaymentHash([0_u8; 32]), ev) + do_pass_along_path(PassAlongPathArgs::new(origin_node, path, 0, *payment_hash, ev) .is_probe() .without_clearing_recipient_events()); let nodes_to_fail_payment: Vec<_> = vec![origin_node].into_iter().chain(path.iter().cloned()).collect(); fail_payment_along_path(nodes_to_fail_payment.as_slice()); + expect_htlc_handling_failed_destinations!( + path.last().unwrap().node.get_and_clear_pending_events(), + &[HTLCDestination::FailedPayment { payment_hash: *payment_hash }] + ); } } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index ac43efe4499..eef69164d8f 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -1556,6 +1556,8 @@ fn test_fee_spike_violation_fails_htlc() { next_local_nonce: None, }; nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &raa_msg); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]); let events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); @@ -1570,7 +1572,7 @@ fn test_fee_spike_violation_fails_htlc() { nodes[1].logger.assert_log("lightning::ln::channel", format!("Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", raa_msg.channel_id), 1); - check_added_monitors!(nodes[1], 2); + check_added_monitors!(nodes[1], 3); } #[test] @@ -6810,6 +6812,9 @@ fn test_update_fulfill_htlc_bolt2_missing_badonion_bit_for_malformed_htlc_messag nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]); check_added_monitors!(nodes[1], 0); commitment_signed_dance!(nodes[1], nodes[0], updates.commitment_signed, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[HTLCDestination::InvalidOnion]); + check_added_monitors(&nodes[1], 1); let events = nodes[1].node.get_and_clear_pending_msg_events(); @@ -6874,6 +6879,9 @@ fn test_update_fulfill_htlc_bolt2_after_malformed_htlc_message_must_forward_upda nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); check_added_monitors!(nodes[2], 0); commitment_signed_dance!(nodes[2], nodes[1], payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::InvalidOnion]); + check_added_monitors(&nodes[2], 1); let events_3 = nodes[2].node.get_and_clear_pending_msg_events(); assert_eq!(events_3.len(), 1); @@ -6945,6 +6953,9 @@ fn test_channel_failed_after_message_with_badonion_node_perm_bits_set() { nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); check_added_monitors!(nodes[2], 0); commitment_signed_dance!(nodes[2], nodes[1], payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), &[HTLCDestination::InvalidOnion]); + check_added_monitors(&nodes[2], 1); let events_3 = nodes[2].node.get_and_clear_pending_msg_events(); assert_eq!(events_3.len(), 1); @@ -10000,9 +10011,7 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e // Outbound dust balance: 4372 sats // Note, we need sent payment to be above outbound dust threshold on counterparty_tx of 2132 sats for _ in 0..dust_outbound_htlc_on_holder_tx { - let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], dust_outbound_htlc_on_holder_tx_msat); - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + route_payment(&nodes[0], &[&nodes[1]], dust_outbound_htlc_on_holder_tx_msat); } } else { // Inbound dust threshold: 2324 sats (`dust_buffer_feerate` * HTLC_SUCCESS_TX_WEIGHT / 1000 + holder's `dust_limit_satoshis`) @@ -10017,9 +10026,7 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e // Outbound dust threshold: 2132 sats (`dust_buffer_feerate` * HTLC_TIMEOUT_TX_WEIGHT / 1000 + counteparty's `dust_limit_satoshis`) // Outbound dust balance: 5000 sats for _ in 0..dust_htlc_on_counterparty_tx - 1 { - let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], dust_htlc_on_counterparty_tx_msat); - nodes[0].node.send_payment_with_route(route, payment_hash, - RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap(); + route_payment(&nodes[0], &[&nodes[1]], dust_htlc_on_counterparty_tx_msat); } } else { // Inbound dust threshold: 2031 sats (`dust_buffer_feerate` * HTLC_TIMEOUT_TX_WEIGHT / 1000 + counteparty's `dust_limit_satoshis`) @@ -10052,6 +10059,9 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e assert_eq!(events.len(), 1); let payment_event = SendEvent::from_event(events.remove(0)); nodes[0].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); + commitment_signed_dance!(nodes[0], nodes[1], payment_event.commitment_msg, false); + expect_pending_htlcs_forwardable!(nodes[0]); + expect_htlc_handling_failed_destinations!(nodes[0].node.get_and_clear_pending_events(), &[HTLCDestination::FailedPayment { payment_hash }]); // With default dust exposure: 5000 sats if on_holder_tx { // Outbound dust balance: 6399 sats diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 1736103def0..6040eb2f85a 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -105,6 +105,9 @@ fn run_onion_failure_test_with_fail_intercept( let update_1_0 = match test_case { 0|100 => { // intermediate node failure; fail backward to 0 + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[expected_htlc_destination.clone().unwrap()]); + check_added_monitors(&nodes[1], 1); let update_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(update_1_0.update_fail_htlcs.len()+update_1_0.update_fail_malformed_htlcs.len()==1 && (update_1_0.update_fail_htlcs.len()==1 || update_1_0.update_fail_malformed_htlcs.len()==1)); update_1_0 @@ -135,12 +138,13 @@ fn run_onion_failure_test_with_fail_intercept( expect_event!(&nodes[2], Event::PaymentClaimable); callback_node(); expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[2], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash.clone() }]); + } else if test_case == 1 || test_case == 3 { + expect_htlc_forward!(&nodes[2]); + expect_htlc_handling_failed_destinations!(nodes[2].node.get_and_clear_pending_events(), vec![expected_htlc_destination.clone().unwrap()]); } + check_added_monitors!(&nodes[2], 1); let update_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); - if test_case == 2 || test_case == 200 { - check_added_monitors!(&nodes[2], 1); - } assert!(update_2_1.update_fail_htlcs.len() == 1); let mut fail_msg = update_2_1.update_fail_htlcs[0].clone(); @@ -301,9 +305,10 @@ fn test_fee_failures() { // because we ignore channel update contents, we will still blame the 2nd channel. let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("fee_insufficient", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { + run_onion_failure_test("fee_insufficient", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; - }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), None); + }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), + Some(HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: channels[1].2 })); // In an earlier version, we spuriously failed to forward payments if the expected feerate // changed between the channel open and the payment. @@ -349,6 +354,8 @@ fn test_onion_failure() { // positive case send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 40000); + let next_hop_failure = HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: channels[1].2 }; + // 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| { @@ -366,7 +373,7 @@ 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_with_writable_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - }, ||{}, true, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); + }, ||{}, true, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); // final node failure let short_channel_id = channels[1].0.contents.short_channel_id; @@ -385,7 +392,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_with_writable_hopdata(new_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); - }, ||{}, false, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); + }, ||{}, false, Some(PERM|22), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); // the following three with run_onion_failure_test_with_fail_intercept() test only the origin node // receiving simulated fail messages @@ -398,7 +405,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.as_ref(), NODE|2, &[0;0]); - }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), None); + }, ||{}, true, Some(NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: false}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); // final node failure run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -418,7 +425,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.as_ref(), PERM|NODE|2, &[0;0]); - }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), None); + }, ||{}, true, Some(PERM|NODE|2), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); // final node failure run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -439,7 +446,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), 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].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), None); + }, true, Some(PERM|NODE|3), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); // final node failure run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { @@ -455,13 +462,13 @@ fn test_onion_failure() { // 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(short_channel_id), None); + Some(BADONION|PERM|4), None, Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); 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(short_channel_id), None); + Some(BADONION|PERM|5), None, Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); 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(short_channel_id), None); + Some(BADONION|PERM|6), None, Some(short_channel_id), Some(HTLCDestination::InvalidOnion)); let short_channel_id = channels[1].0.contents.short_channel_id; let chan_update = ChannelUpdate::dummy(short_channel_id); @@ -478,7 +485,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data); }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), None); + Some(short_channel_id), Some(next_hop_failure.clone())); // Check we can still handle onion failures that include channel updates without a type prefix let err_data_without_type = chan_update.encode_with_len(); @@ -490,7 +497,7 @@ fn test_onion_failure() { msg.reason = onion_utils::build_first_hop_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type); }, ||{}, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), None); + Some(short_channel_id), Some(next_hop_failure.clone())); 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| { @@ -500,7 +507,7 @@ 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.as_ref(), PERM|8, &[0;0]); // short_channel_id from the processing node - }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); + }, ||{}, true, Some(PERM|8), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); 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| { @@ -510,13 +517,13 @@ 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.as_ref(), PERM|9, &[0;0]); // short_channel_id from the processing node - }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), None); + }, ||{}, true, Some(PERM|9), Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent: true}), Some(short_channel_id), Some(next_hop_failure.clone())); let mut bogus_route = route.clone(); bogus_route.paths[0].hops[1].short_channel_id -= 1; let short_channel_id = bogus_route.paths[0].hops[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::ChannelFailure{short_channel_id, is_permanent:true}), Some(short_channel_id), None); + run_onion_failure_test("unknown_next_peer", 100, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(PERM|10), + Some(NetworkUpdate::ChannelFailure{short_channel_id, is_permanent:true}), Some(short_channel_id), Some(HTLCDestination::UnknownNextHop { requested_forward_scid: short_channel_id })); let short_channel_id = channels[1].0.contents.short_channel_id; let amt_to_forward = nodes[1].node.per_peer_state.read().unwrap().get(&nodes[2].node.get_our_node_id()) @@ -525,9 +532,9 @@ fn test_onion_failure() { let mut bogus_route = route.clone(); let route_len = bogus_route.paths[0].hops.len(); bogus_route.paths[0].hops[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), + run_onion_failure_test("amount_below_minimum", 100, &nodes, &bogus_route, &payment_hash, &payment_secret, |_| {}, ||{}, true, Some(UPDATE|11), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), None); + Some(short_channel_id), Some(next_hop_failure.clone())); // Clear pending payments so that the following positive test has the correct payment hash. for node in nodes.iter() { @@ -542,25 +549,25 @@ fn test_onion_failure() { // We ignore channel update contents in onion errors, so will blame the 2nd channel even though // the first node is the one that messed up. let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("fee_insufficient", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { + run_onion_failure_test("fee_insufficient", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.amount_msat -= 1; - }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), None); + }, || {}, true, Some(UPDATE|12), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), Some(next_hop_failure.clone())); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("incorrect_cltv_expiry", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { + run_onion_failure_test("incorrect_cltv_expiry", 100, &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::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), None); + }, || {}, true, Some(UPDATE|13), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false}), Some(short_channel_id), Some(next_hop_failure.clone())); 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| { + run_onion_failure_test("expiry_too_soon", 100, &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::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), None); + Some(short_channel_id), Some(next_hop_failure.clone())); run_onion_failure_test("unknown_payment_hash", 2, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { nodes[2].node.fail_htlc_backwards(&payment_hash); @@ -572,9 +579,10 @@ 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); - }, || {}, false, Some(0x4000 | 15), None, None, None); + }, || {}, false, Some(0x4000 | 15), None, None, Some(HTLCDestination::FailedPayment { payment_hash })); run_onion_failure_test("final_incorrect_cltv_expiry", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { + nodes[1].node.process_pending_update_add_htlcs(); for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { match f { @@ -584,9 +592,10 @@ fn test_onion_failure() { } } } - }, true, Some(18), None, Some(channels[1].0.contents.short_channel_id), None); + }, true, Some(18), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCDestination::FailedPayment { payment_hash })); run_onion_failure_test("final_incorrect_htlc_amount", 1, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { + nodes[1].node.process_pending_update_add_htlcs(); // violate amt_to_forward > msg.amount_msat for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { @@ -597,17 +606,17 @@ fn test_onion_failure() { } } } - }, true, Some(19), None, Some(channels[1].0.contents.short_channel_id), None); + }, true, Some(19), None, Some(channels[1].0.contents.short_channel_id), Some(HTLCDestination::FailedPayment { payment_hash })); let short_channel_id = channels[1].0.contents.short_channel_id; - run_onion_failure_test("channel_disabled", 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { + run_onion_failure_test("channel_disabled", 100, &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()); nodes[2].node.peer_disconnected(nodes[1].node.get_our_node_id()); }, true, Some(UPDATE|7), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), None); - run_onion_failure_test("channel_disabled", 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { + Some(short_channel_id), Some(next_hop_failure.clone())); + run_onion_failure_test("channel_disabled", 100, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || { // disconnect event to the channel between nodes[1] ~ nodes[2] for _ in 0..DISABLE_GOSSIP_TICKS + 1 { nodes[1].node.timer_tick_occurred(); @@ -617,10 +626,10 @@ fn test_onion_failure() { nodes[2].node.get_and_clear_pending_msg_events(); }, true, Some(UPDATE|20), Some(NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }), - Some(short_channel_id), None); + Some(short_channel_id), Some(next_hop_failure.clone())); reconnect_nodes(ReconnectArgs::new(&nodes[1], &nodes[2])); - run_onion_failure_test("expiry_too_far", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { + run_onion_failure_test("expiry_too_far", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let mut route = route.clone(); let height = nodes[2].best_block_info().1; @@ -632,7 +641,7 @@ fn test_onion_failure() { let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); msg.cltv_expiry = htlc_cltv; msg.onion_routing_packet = onion_packet; - }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), None); + }, ||{}, true, Some(21), Some(NetworkUpdate::NodeFailure{node_id: route.paths[0].hops[0].pubkey, is_permanent: true}), Some(route.paths[0].hops[0].short_channel_id), Some(next_hop_failure.clone())); run_onion_failure_test_with_fail_intercept("mpp_timeout", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { // Tamper returning error message @@ -686,7 +695,7 @@ fn test_onion_failure() { short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: false, }), - Some(channels[1].0.contents.short_channel_id), None); + Some(channels[1].0.contents.short_channel_id), Some(next_hop_failure.clone())); run_onion_failure_test_with_fail_intercept("0-length channel update in final node UPDATE onion failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); @@ -860,9 +869,9 @@ fn do_test_onion_failure_stale_channel_update(announce_for_forwarding: bool) { let short_channel_id = channel_to_update.1; let network_update = NetworkUpdate::ChannelFailure { short_channel_id, is_permanent: false }; run_onion_failure_test( - name, 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {}, true, + name, 100, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {}, true, Some(error_code), Some(network_update), Some(short_channel_id), - None, + Some(HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: channel_to_update.0 }), ); }; @@ -1143,6 +1152,8 @@ fn test_phantom_onion_hmac_failure() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &update_add); commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); + expect_pending_htlcs_forwardable_ignore!(nodes[1]); + nodes[1].node.process_pending_update_add_htlcs(); // Modify the payload so the phantom hop's HMAC is bogus. let sha256_of_onion = { @@ -1161,7 +1172,6 @@ fn test_phantom_onion_hmac_failure() { _ => panic!("Unexpected forward"), } }; - expect_pending_htlcs_forwardable_ignore!(nodes[1]); nodes[1].node.process_pending_htlc_forwards(); expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash }]); nodes[1].node.process_pending_htlc_forwards(); @@ -1205,6 +1215,8 @@ fn test_phantom_invalid_onion_payload() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &update_add); commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); + expect_pending_htlcs_forwardable_ignore!(nodes[1]); + nodes[1].node.process_pending_update_add_htlcs(); // Modify the onion packet to have an invalid payment amount. for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { @@ -1237,7 +1249,6 @@ fn test_phantom_invalid_onion_payload() { } } } - expect_pending_htlcs_forwardable_ignore!(nodes[1]); nodes[1].node.process_pending_htlc_forwards(); expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash }]); nodes[1].node.process_pending_htlc_forwards(); @@ -1280,6 +1291,8 @@ fn test_phantom_final_incorrect_cltv_expiry() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &update_add); commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); + expect_pending_htlcs_forwardable_ignore!(nodes[1]); + nodes[1].node.process_pending_update_add_htlcs(); // Modify the payload so the phantom hop's HMAC is bogus. for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { @@ -1294,7 +1307,6 @@ fn test_phantom_final_incorrect_cltv_expiry() { } } } - expect_pending_htlcs_forwardable_ignore!(nodes[1]); nodes[1].node.process_pending_htlc_forwards(); expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash }]); nodes[1].node.process_pending_htlc_forwards(); @@ -1391,6 +1403,12 @@ fn test_phantom_failure_modified_cltv() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &update_add); commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::UnknownNextHop { requested_forward_scid: phantom_scid }] + ); + check_added_monitors(&nodes[1], 1); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(update_1.update_fail_htlcs.len() == 1); @@ -1434,6 +1452,12 @@ fn test_phantom_failure_expires_too_soon() { connect_blocks(&nodes[1], CLTV_FAR_FAR_AWAY); nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &update_add); commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::UnknownNextHop { requested_forward_scid: phantom_scid }] + ); + check_added_monitors(&nodes[1], 1); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(update_1.update_fail_htlcs.len() == 1); @@ -1498,7 +1522,7 @@ fn test_phantom_failure_too_low_recv_amt() { #[test] fn test_phantom_dust_exposure_failure() { - do_test_phantom_dust_exposure_failure(false); + //do_test_phantom_dust_exposure_failure(false); do_test_phantom_dust_exposure_failure(true); } @@ -1522,7 +1546,7 @@ fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) { // Get the route with an amount exceeding the dust exposure threshold of nodes[1]. let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(max_dust_exposure + 1)); - let (mut route, _) = get_phantom_route!(nodes, max_dust_exposure + 1, channel); + let (mut route, phantom_scid) = get_phantom_route!(nodes, max_dust_exposure + 1, channel); // Route the HTLC through to the destination. nodes[0].node.send_payment_with_route(route.clone(), payment_hash, @@ -1533,6 +1557,12 @@ fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &update_add); commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::UnknownNextHop { requested_forward_scid: phantom_scid }] + ); + check_added_monitors(&nodes[1], 1); let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(update_1.update_fail_htlcs.len() == 1); @@ -1543,8 +1573,7 @@ fn do_test_phantom_dust_exposure_failure(multiplier_dust_limit: bool) { // Ensure the payment fails with the expected error. let err_data = 0u16.to_be_bytes(); let mut fail_conditions = PaymentFailedConditions::new() - .blamed_scid(route.paths[0].hops.last().as_ref().unwrap().short_channel_id) - .blamed_chan_closed(false) + .blamed_scid(phantom_scid) .expected_htlc_error_data(0x1000 | 7, &err_data); expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); } diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 0c9c5d0e920..430f9014703 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -494,6 +494,8 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { let update_add_1 = update_1.update_add_htlcs[0].clone(); nodes[3].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &update_add_1); commitment_signed_dance!(nodes[3], nodes[1], update_1.commitment_signed, false, true); + expect_pending_htlcs_forwardable_ignore!(nodes[3]); + nodes[3].node.process_pending_update_add_htlcs(); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); for (_, pending_forwards) in nodes[3].node.forward_htlcs.lock().unwrap().iter_mut() { @@ -514,7 +516,7 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { } } } - expect_pending_htlcs_forwardable!(nodes[3]); + nodes[3].node.process_pending_htlc_forwards(); // Pay along nodes[2] route.paths[0].hops[0].pubkey = nodes[2].node.get_our_node_id(); @@ -540,6 +542,8 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { let update_add_3 = update_3.update_add_htlcs[0].clone(); nodes[3].node.handle_update_add_htlc(nodes[2].node.get_our_node_id(), &update_add_3); commitment_signed_dance!(nodes[3], nodes[2], update_3.commitment_signed, false, true); + expect_pending_htlcs_forwardable_ignore!(nodes[3]); + nodes[3].node.process_pending_update_add_htlcs(); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); for (_, pending_forwards) in nodes[3].node.forward_htlcs.lock().unwrap().iter_mut() { @@ -560,7 +564,8 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { } } } - expect_pending_htlcs_forwardable!(nodes[3]); + nodes[3].node.process_pending_htlc_forwards(); + expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[3], vec![HTLCDestination::FailedPayment { payment_hash }]); check_added_monitors!(nodes[3], 1); // Fail back along nodes[2] @@ -575,7 +580,6 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { commitment_signed_dance!(nodes[0], nodes[2], update_fail_1.commitment_signed, false); expect_payment_failed_conditions(&nodes[0], payment_hash, true, PaymentFailedConditions::new()); - expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[3], vec![HTLCDestination::FailedPayment { payment_hash }]); } @@ -656,6 +660,12 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_id_2}] + ); + check_added_monitors(&nodes[1], 1); // nodes[1] now immediately fails the HTLC as the next-hop channel is disconnected let _ = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); @@ -1281,7 +1291,7 @@ fn successful_probe_yields_event() { let res = nodes[0].node.send_probe(route.paths[0].clone()).unwrap(); - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let expected_route: &[(&[&Node], PaymentHash)] = &[(&[&nodes[1], &nodes[2]], res.0)]; send_probe_along_route(&nodes[0], expected_route); @@ -1422,7 +1432,7 @@ fn preflight_probes_yield_event_skip_private_hop() { let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value); let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap(); - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2], &nodes[3]]]; + let expected_route: &[(&[&Node], PaymentHash)] = &[(&[&nodes[1], &nodes[2], &nodes[3]], res[0].0)]; assert_eq!(res.len(), expected_route.len()); @@ -1468,7 +1478,7 @@ fn preflight_probes_yield_event() { let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value); let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap(); - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; + let expected_route: &[(&[&Node], PaymentHash)] = &[(&[&nodes[1], &nodes[3]], res[0].0), (&[&nodes[2], &nodes[3]], res[1].0)]; assert_eq!(res.len(), expected_route.len()); @@ -1515,7 +1525,7 @@ fn preflight_probes_yield_event_and_skip() { let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value); let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap(); - let expected_route : &[&[&Node]] = &[&[&nodes[1], &nodes[2], &nodes[4]]]; + let expected_route: &[(&[&Node], PaymentHash)] = &[(&[&nodes[1], &nodes[2], &nodes[4]], res[0].0)]; // We check that only one probe was sent, the other one was skipped due to limited liquidity. assert_eq!(res.len(), 1); @@ -1902,6 +1912,7 @@ fn do_test_intercepted_payment(test: InterceptTest) { }; nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], &payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); // Check that we generate the PaymentIntercepted event when an intercept forward is detected. let events = nodes[1].node.get_and_clear_pending_events(); @@ -2086,6 +2097,7 @@ fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) { for (idx, ev) in events.into_iter().enumerate() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &ev.msgs[0]); do_commitment_signed_dance(&nodes[1], &nodes[0], &ev.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); @@ -2894,8 +2906,10 @@ fn no_extra_retries_on_back_to_back_fail() { let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - let chan_1_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0.contents.short_channel_id; - let chan_2_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0.contents.short_channel_id; + let chan_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0); + let chan_1_scid = chan_1.0.contents.short_channel_id; + let chan_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0); + let chan_2_scid = chan_2.0.contents.short_channel_id; let amt_msat = 200_000_000; let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat); @@ -2964,66 +2978,58 @@ fn no_extra_retries_on_back_to_back_fail() { route.route_params = Some(retry_params.clone()); nodes[0].router.expect_find_route(retry_params, Ok(route.clone())); + // We can't use the commitment_signed_dance macro helper because in this test we'll be sending + // two HTLCs back-to-back on the same channel, and the macro only expects to handle one at a + // time. nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap(); - let htlc_updates = SendEvent::from_node(&nodes[0]); + + let first_htlc_updates = SendEvent::from_node(&nodes[0]); check_added_monitors!(nodes[0], 1); - assert_eq!(htlc_updates.msgs.len(), 1); + assert_eq!(first_htlc_updates.msgs.len(), 1); - nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &htlc_updates.msgs[0]); - nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &htlc_updates.commitment_msg); + nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &first_htlc_updates.msgs[0]); + nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &first_htlc_updates.commitment_msg); check_added_monitors!(nodes[1], 1); - let (bs_first_raa, bs_first_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + let (bs_first_raa, bs_first_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &bs_first_raa); check_added_monitors!(nodes[0], 1); + let second_htlc_updates = SendEvent::from_node(&nodes[0]); + assert_eq!(second_htlc_updates.msgs.len(), 1); nodes[0].node.handle_commitment_signed(nodes[1].node.get_our_node_id(), &bs_first_cs); check_added_monitors!(nodes[0], 1); + let as_first_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &as_first_raa); + check_added_monitors!(nodes[1], 1); nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &second_htlc_updates.msgs[0]); nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &second_htlc_updates.commitment_msg); check_added_monitors!(nodes[1], 1); - let bs_second_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id()); - - nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &as_first_raa); - check_added_monitors!(nodes[1], 1); - let bs_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + let (bs_second_raa, bs_second_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &bs_second_raa); check_added_monitors!(nodes[0], 1); - - nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[0]); - nodes[0].node.handle_commitment_signed(nodes[1].node.get_our_node_id(), &bs_fail_update.commitment_signed); + nodes[0].node.handle_commitment_signed(nodes[1].node.get_our_node_id(), &bs_second_cs); check_added_monitors!(nodes[0], 1); - let (as_second_raa, as_third_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id()); + let as_second_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id()); nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &as_second_raa); check_added_monitors!(nodes[1], 1); - let bs_second_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); - - nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &as_third_cs); - check_added_monitors!(nodes[1], 1); - let bs_third_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id()); - nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &bs_second_fail_update.update_fail_htlcs[0]); - nodes[0].node.handle_commitment_signed(nodes[1].node.get_our_node_id(), &bs_second_fail_update.commitment_signed); - check_added_monitors!(nodes[0], 1); - - nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &bs_third_raa); - check_added_monitors!(nodes[0], 1); - let (as_third_raa, as_fourth_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id()); - - nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &as_third_raa); - check_added_monitors!(nodes[1], 1); - nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &as_fourth_cs); - check_added_monitors!(nodes[1], 1); - let bs_fourth_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id()); + expect_pending_htlcs_forwardable!(nodes[1]); + let next_hop_failure = HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_2.2 }; + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[next_hop_failure.clone(), next_hop_failure.clone()]); + check_added_monitors(&nodes[1], 1); - nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &bs_fourth_raa); - check_added_monitors!(nodes[0], 1); + let bs_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + assert_eq!(bs_fail_update.update_fail_htlcs.len(), 2); + nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[0]); + nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[1]); + commitment_signed_dance!(nodes[0], nodes[1], bs_fail_update.commitment_signed, false); // At this point A has sent two HTLCs which both failed due to lack of fee. It now has two // pending `PaymentPathFailed` events, one with `all_paths_failed` unset, and the second @@ -3064,6 +3070,10 @@ fn no_extra_retries_on_back_to_back_fail() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &retry_htlc_updates.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], &retry_htlc_updates.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[next_hop_failure.clone()]); + check_added_monitors(&nodes[1], 1); + let bs_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[0]); commitment_signed_dance!(nodes[0], nodes[1], &bs_fail_update.commitment_signed, false, true); @@ -3099,8 +3109,10 @@ fn test_simple_partial_retry() { let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - let chan_1_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0.contents.short_channel_id; - let chan_2_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0.contents.short_channel_id; + let chan_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0); + let chan_1_scid = chan_1.0.contents.short_channel_id; + let chan_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0); + let chan_2_scid = chan_2.0.contents.short_channel_id; let amt_msat = 200_000_000; let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[2], amt_msat); @@ -3169,52 +3181,64 @@ fn test_simple_partial_retry() { route.route_params = Some(retry_params.clone()); nodes[0].router.expect_find_route(retry_params, Ok(route.clone())); + // We can't use the commitment_signed_dance macro helper because in this test we'll be sending + // two HTLCs back-to-back on the same channel, and the macro only expects to handle one at a + // time. nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap(); - let htlc_updates = SendEvent::from_node(&nodes[0]); + let first_htlc_updates = SendEvent::from_node(&nodes[0]); check_added_monitors!(nodes[0], 1); - assert_eq!(htlc_updates.msgs.len(), 1); + assert_eq!(first_htlc_updates.msgs.len(), 1); - nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &htlc_updates.msgs[0]); - nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &htlc_updates.commitment_msg); + nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &first_htlc_updates.msgs[0]); + nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &first_htlc_updates.commitment_msg); check_added_monitors!(nodes[1], 1); - let (bs_first_raa, bs_first_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + let (bs_first_raa, bs_first_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &bs_first_raa); check_added_monitors!(nodes[0], 1); + let second_htlc_updates = SendEvent::from_node(&nodes[0]); + assert_eq!(second_htlc_updates.msgs.len(), 1); nodes[0].node.handle_commitment_signed(nodes[1].node.get_our_node_id(), &bs_first_cs); check_added_monitors!(nodes[0], 1); - let as_first_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id()); - - nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &second_htlc_updates.msgs[0]); - nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &second_htlc_updates.commitment_msg); - check_added_monitors!(nodes[1], 1); - let bs_second_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id()); + let as_first_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id()); nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &as_first_raa); check_added_monitors!(nodes[1], 1); - let bs_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); - - nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &bs_second_raa); - check_added_monitors!(nodes[0], 1); - - nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[0]); - nodes[0].node.handle_commitment_signed(nodes[1].node.get_our_node_id(), &bs_fail_update.commitment_signed); - check_added_monitors!(nodes[0], 1); - let (as_second_raa, as_third_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id()); - nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &as_second_raa); - check_added_monitors!(nodes[1], 1); - - nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &as_third_cs); - check_added_monitors!(nodes[1], 1); + nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &second_htlc_updates.msgs[0]); + commitment_signed_dance!(nodes[1], nodes[0], second_htlc_updates.commitment_msg, false); - let bs_third_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id()); + expect_pending_htlcs_forwardable!(nodes[1]); + let next_hop_failure = HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_2.2 }; + expect_htlc_handling_failed_destinations!(nodes[1].node.get_and_clear_pending_events(), &[next_hop_failure.clone()]); + check_added_monitors(&nodes[1], 2); - nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &bs_third_raa); - check_added_monitors!(nodes[0], 1); + { + let mut msg_events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 2); + let mut handle_update_htlcs = |event: MessageSendEvent| { + if let MessageSendEvent::UpdateHTLCs { node_id, updates } = event { + if node_id == nodes[0].node.get_our_node_id() { + assert_eq!(updates.update_fail_htlcs.len(), 1); + nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); + commitment_signed_dance!(nodes[0], nodes[1], &updates.commitment_signed, false); + } else if node_id == nodes[2].node.get_our_node_id() { + assert_eq!(updates.update_add_htlcs.len(), 1); + nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &updates.update_add_htlcs[0]); + commitment_signed_dance!(nodes[2], nodes[1], &updates.commitment_signed, false); + } else { + panic!("Unexpected node_id for UpdateHTLCs send"); + } + } else { + panic!("Unexpected event"); + } + }; + handle_update_htlcs(msg_events.remove(0)); + handle_update_htlcs(msg_events.remove(0)); + } let mut events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 2); @@ -3240,10 +3264,9 @@ fn test_simple_partial_retry() { expect_pending_htlcs_forwardable!(nodes[1]); check_added_monitors!(nodes[1], 1); - let bs_forward_update = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id()); - nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &bs_forward_update.update_add_htlcs[0]); - nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &bs_forward_update.update_add_htlcs[1]); - commitment_signed_dance!(nodes[2], nodes[1], &bs_forward_update.commitment_signed, false); + let bs_second_forward_update = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id()); + nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &bs_second_forward_update.update_add_htlcs[0]); + commitment_signed_dance!(nodes[2], nodes[1], &bs_second_forward_update.commitment_signed, false); expect_pending_htlcs_forwardable!(nodes[2]); expect_payment_claimable!(nodes[2], payment_hash, payment_secret, amt_msat); @@ -3378,6 +3401,13 @@ fn test_threaded_payment_retries() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &send_event.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], send_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + nodes[1].node.process_pending_htlc_forwards(); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::UnknownNextHop { requested_forward_scid: route.paths[0].hops[1].short_channel_id }] + ); + check_added_monitors(&nodes[1], 1); // Note that we only push one route into `expect_find_route` at a time, because that's all // the retries (should) need. If the bug is reintroduced "real" routes may be selected, but @@ -4055,6 +4085,12 @@ fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) { nodes[2].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &c_recv_ev.msgs[0]); commitment_signed_dance!(nodes[2], nodes[0], c_recv_ev.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[2]); + expect_htlc_handling_failed_destinations!( + nodes[2].node.get_and_clear_pending_events(), + &[HTLCDestination::NextHopChannel { node_id: Some(nodes[3].node.get_our_node_id()), channel_id: chan_id_cd }] + ); + check_added_monitors(&nodes[2], 1); let cs_fail = get_htlc_update_msgs(&nodes[2], &nodes[0].node.get_our_node_id()); nodes[0].node.handle_update_fail_htlc(nodes[2].node.get_our_node_id(), &cs_fail.update_fail_htlcs[0]); diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index 55e97e06894..5c45498753b 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -71,6 +71,12 @@ fn test_priv_forwarding_rejection() { let payment_event = SendEvent::from_event(nodes[0].node.get_and_clear_pending_msg_events().remove(0)); nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_id_2 }] + ); + check_added_monitors(&nodes[1], 1); let htlc_fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(htlc_fail_updates.update_add_htlcs.is_empty()); @@ -435,6 +441,12 @@ fn test_inbound_scid_privacy() { assert_eq!(nodes[1].node.get_our_node_id(), payment_event.node_id); nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, true, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: last_hop[0].channel_id }] + ); + check_added_monitors(&nodes[1], 1); nodes[1].logger.assert_log_regex("lightning::ln::channelmanager", regex::Regex::new(r"Refusing to forward over real channel SCID as our counterparty requested").unwrap(), 1); @@ -514,6 +526,13 @@ fn test_scid_alias_returned() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &as_updates.update_add_htlcs[0]); commitment_signed_dance!(nodes[1], nodes[0], &as_updates.commitment_signed, false, true); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan.0.channel_id }] + ); + check_added_monitors(&nodes[1], 1); + let bs_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &bs_updates.update_fail_htlcs[0]); commitment_signed_dance!(nodes[0], nodes[1], bs_updates.commitment_signed, false, true); diff --git a/lightning/src/ln/reload_tests.rs b/lightning/src/ln/reload_tests.rs index 28465a09660..039fe85197f 100644 --- a/lightning/src/ln/reload_tests.rs +++ b/lightning/src/ln/reload_tests.rs @@ -744,10 +744,6 @@ fn test_forwardable_regen() { nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false); - // There is already a PendingHTLCsForwardable event "pending" so another one will not be - // generated - assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); - // Now restart nodes[1] and make sure it regenerates a single PendingHTLCsForwardable nodes[0].node.peer_disconnected(nodes[1].node.get_our_node_id()); nodes[2].node.peer_disconnected(nodes[1].node.get_our_node_id()); @@ -1017,9 +1013,12 @@ fn do_forwarded_payment_no_manager_persistence(use_cs_commitment: bool, claim_ht // present when we serialized. let node_encoded = nodes[1].node.encode(); + expect_pending_htlcs_forwardable_ignore!(nodes[1]); + let mut intercept_id = None; let mut expected_outbound_amount_msat = None; if use_intercept { + nodes[1].node.process_pending_update_add_htlcs(); let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); match events[0] { @@ -1033,7 +1032,7 @@ fn do_forwarded_payment_no_manager_persistence(use_cs_commitment: bool, claim_ht nodes[2].node.get_our_node_id(), expected_outbound_amount_msat.unwrap()).unwrap(); } - expect_pending_htlcs_forwardable!(nodes[1]); + nodes[1].node.process_pending_htlc_forwards(); let payment_event = SendEvent::from_node(&nodes[1]); nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs index 9fd428329af..ba682f68375 100644 --- a/lightning/src/ln/shutdown_tests.rs +++ b/lightning/src/ln/shutdown_tests.rs @@ -465,6 +465,12 @@ fn do_htlc_fail_async_shutdown(blinded_recipient: bool) { check_added_monitors!(nodes[1], 1); nodes[1].node.handle_shutdown(nodes[0].node.get_our_node_id(), &node_0_shutdown); commitment_signed_dance!(nodes[1], nodes[0], (), false, true, false, false); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_htlc_handling_failed_destinations!( + nodes[1].node.get_and_clear_pending_events(), + &[HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id: chan_2.2 }] + ); + check_added_monitors(&nodes[1], 1); let updates_2 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(updates_2.update_add_htlcs.is_empty()); From a52b43bb242643bae6d3a782cc5c1e51b8cf37a7 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 26 Jul 2024 15:44:36 -0400 Subject: [PATCH 03/23] Fix outdated static invoice docs. --- lightning/src/offers/static_invoice.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index 39c17eb3bcc..fdeffcb0c20 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -49,7 +49,7 @@ use crate::offers::invoice::is_expired; use crate::prelude::*; /// Static invoices default to expiring after 2 weeks. -const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(3600 * 24 * 14); +pub const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(3600 * 24 * 14); /// Tag for the hash function used when signing a [`StaticInvoice`]'s merkle root. pub const SIGNATURE_TAG: &'static str = concat!("lightning", "static_invoice", "signature"); @@ -102,8 +102,8 @@ pub struct StaticInvoiceBuilder<'a> { impl<'a> StaticInvoiceBuilder<'a> { /// Initialize a [`StaticInvoiceBuilder`] from the given [`Offer`]. /// - /// Unless [`StaticInvoiceBuilder::relative_expiry`] is set, the invoice will expire 24 hours - /// after `created_at`. + /// The invoice's expiration will default to [`DEFAULT_RELATIVE_EXPIRY`] after `created_at` unless + /// overridden by [`StaticInvoiceBuilder::relative_expiry`]. pub fn for_offer_using_derived_keys( offer: &'a Offer, payment_paths: Vec, message_paths: Vec, created_at: Duration, expanded_key: &ExpandedKey, From 0684b86d7faa5369d0da658dd4136a5e1d2a3909 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 29 Oct 2024 15:37:19 -0400 Subject: [PATCH 04/23] Don't take() outbound invoice requests on retry Prior to this patch, we would take() the invoice request stored for AwaitingInvoice outbound payments when retrying sending the invoice request onion message. This doesn't work for async payments because we need to keep the invoice request stored for inclusion in the payment onion. Therefore, clone it instead of take()ing it. --- lightning/src/ln/channelmanager.rs | 4 +++- lightning/src/ln/outbound_payment.rs | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 314dbad5a3b..e6f21e81c60 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9941,6 +9941,7 @@ where let retryable_invoice_request = RetryableInvoiceRequest { invoice_request: invoice_request.clone(), nonce, + needs_retry: true, }; self.pending_outbound_payments .add_new_awaiting_invoice( @@ -11822,7 +11823,7 @@ where .pending_outbound_payments .release_invoice_requests_awaiting_invoice() { - let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request; + let RetryableInvoiceRequest { invoice_request, nonce, .. } = retryable_invoice_request; let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); let context = MessageContext::Offers(OffersContext::OutboundPayment { payment_id, @@ -12157,6 +12158,7 @@ where let retryable_invoice_request = RetryableInvoiceRequest { invoice_request: invoice_request.clone(), nonce, + needs_retry: true, }; self.pending_outbound_payments .received_offer(payment_id, Some(retryable_invoice_request)) diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index c0dea7df52d..de141223a3d 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -134,13 +134,16 @@ pub(crate) enum PendingOutboundPayment { }, } +#[derive(Clone)] pub(crate) struct RetryableInvoiceRequest { pub(crate) invoice_request: InvoiceRequest, pub(crate) nonce: Nonce, + pub(super) needs_retry: bool, } impl_writeable_tlv_based!(RetryableInvoiceRequest, { (0, invoice_request, required), + (1, needs_retry, (default_value, true)), (2, nonce, required), }); @@ -760,7 +763,12 @@ pub(super) struct OutboundPayments { impl OutboundPayments { pub(super) fn new(pending_outbound_payments: HashMap) -> Self { let has_invoice_requests = pending_outbound_payments.values().any(|payment| { - matches!(payment, PendingOutboundPayment::AwaitingInvoice { retryable_invoice_request: Some(_), .. }) + matches!( + payment, + PendingOutboundPayment::AwaitingInvoice { + retryable_invoice_request: Some(invreq), .. + } if invreq.needs_retry + ) }); Self { @@ -2229,11 +2237,12 @@ impl OutboundPayments { .iter_mut() .filter_map(|(payment_id, payment)| { if let PendingOutboundPayment::AwaitingInvoice { - retryable_invoice_request, .. + retryable_invoice_request: Some(invreq), .. } = payment { - retryable_invoice_request.take().map(|retryable_invoice_request| { - (*payment_id, retryable_invoice_request) - }) + if invreq.needs_retry { + invreq.needs_retry = false; + Some((*payment_id, invreq.clone())) + } else { None } } else { None } From 5ecbfa35253c17798e6e4d2a247a2123a8641fc9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 13 Nov 2024 15:49:29 -0500 Subject: [PATCH 05/23] Fix failure to abandon async payments on invalid static invoice Prior to this fix, we would attempt to mark outbound async payments as abandoned but silently fail because they were in state AwaitingInvoice, which the mark_abandoned utility doesn't currently work for. These payments would eventually be removed by the remove_stale_payments method, but there would be a delay in generating the PaymentFailed event. Move to manually removing the outbound payment entry. --- lightning/src/ln/outbound_payment.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index de141223a3d..070a09fde7b 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1016,17 +1016,16 @@ impl OutboundPayments { ) -> Result<(), Bolt12PaymentError> where ES::Target: EntropySource { macro_rules! abandon_with_entry { ($payment: expr, $reason: expr) => { - $payment.get_mut().mark_abandoned($reason); - if let PendingOutboundPayment::Abandoned { reason, .. } = $payment.get() { - if $payment.get().remaining_parts() == 0 { - pending_events.lock().unwrap().push_back((events::Event::PaymentFailed { - payment_id, - payment_hash: None, - reason: *reason, - }, None)); - $payment.remove(); - } - } + assert!( + matches!($payment.get(), PendingOutboundPayment::AwaitingInvoice { .. }), + "Generating PaymentFailed for unexpected outbound payment type can result in funds loss" + ); + pending_events.lock().unwrap().push_back((events::Event::PaymentFailed { + payment_id, + payment_hash: None, + reason: Some($reason), + }, None)); + $payment.remove(); } } From ddc5154c31848b4b4a8f9c5b19b6443ddb710333 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 24 Jun 2024 13:36:55 -0400 Subject: [PATCH 06/23] Make create_blinded_payment_paths methods amount optional. Useful for creating payment paths for static invoices which are typically amount-less. --- fuzz/src/chanmon_consistency.rs | 2 +- fuzz/src/full_stack.rs | 2 +- lightning/src/ln/channelmanager.rs | 6 +++--- lightning/src/routing/router.rs | 10 +++++----- lightning/src/util/test_utils.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index c2a49b8ee24..26c9fb2177d 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -133,7 +133,7 @@ impl Router for FuzzRouter { fn create_blinded_payment_paths( &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, - _amount_msats: u64, _secp_ctx: &Secp256k1, + _amount_msats: Option, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 1b3e576da6c..74eacdabd5e 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -160,7 +160,7 @@ impl Router for FuzzRouter { fn create_blinded_payment_paths( &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, - _amount_msats: u64, _secp_ctx: &Secp256k1, + _amount_msats: Option, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e6f21e81c60..4fae10cc66c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10077,7 +10077,7 @@ where Ok((payment_hash, payment_secret)) => { let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); let payment_paths = self.create_blinded_payment_paths( - amount_msats, payment_secret, payment_context + Some(amount_msats), payment_secret, payment_context ) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -10384,7 +10384,7 @@ where /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. fn create_blinded_payment_paths( - &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext + &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext ) -> Result, ()> { let expanded_key = &self.inbound_payment_key; let entropy = &*self.entropy_source; @@ -11955,7 +11955,7 @@ where invoice_request: invoice_request.fields(), }); let payment_paths = match self.create_blinded_payment_paths( - amount_msats, payment_secret, payment_context + Some(amount_msats), payment_secret, payment_context ) { Ok(payment_paths) => payment_paths, Err(()) => { diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 78a93aa0d39..a257d2b4abe 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -95,7 +95,7 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size T: secp256k1::Signing + secp256k1::Verification > ( &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: u64, secp_ctx: &Secp256k1 + amount_msats: Option, secp_ctx: &Secp256k1 ) -> Result, ()> { // Limit the number of blinded paths that are computed. const MAX_PAYMENT_PATHS: usize = 3; @@ -120,9 +120,9 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size let paths = first_hops.into_iter() .filter(|details| details.counterparty.features.supports_route_blinding()) - .filter(|details| amount_msats <= details.inbound_capacity_msat) - .filter(|details| amount_msats >= details.inbound_htlc_minimum_msat.unwrap_or(0)) - .filter(|details| amount_msats <= details.inbound_htlc_maximum_msat.unwrap_or(u64::MAX)) + .filter(|details| amount_msats.unwrap_or(0) <= details.inbound_capacity_msat) + .filter(|details| amount_msats.unwrap_or(u64::MAX) >= details.inbound_htlc_minimum_msat.unwrap_or(0)) + .filter(|details| amount_msats.unwrap_or(0) <= details.inbound_htlc_maximum_msat.unwrap_or(u64::MAX)) // Limit to peers with announced channels unless the recipient is unannounced. .filter(|details| network_graph .node(&NodeId::from_pubkey(&details.counterparty.node_id)) @@ -218,7 +218,7 @@ pub trait Router { T: secp256k1::Signing + secp256k1::Verification > ( &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: u64, secp_ctx: &Secp256k1 + amount_msats: Option, secp_ctx: &Secp256k1 ) -> Result, ()>; } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 07b2b19b0d6..3d3dd587609 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -252,7 +252,7 @@ impl<'a> Router for TestRouter<'a> { T: secp256k1::Signing + secp256k1::Verification >( &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, - amount_msats: u64, secp_ctx: &Secp256k1, + amount_msats: Option, secp_ctx: &Secp256k1, ) -> Result, ()> { let mut expected_paths = self.next_blinded_payment_paths.lock().unwrap(); if expected_paths.is_empty() { From 2538d3feb0a4bd71f66ccbb8a379a69f72c70ee9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 8 Jul 2024 16:56:24 -0400 Subject: [PATCH 07/23] Factor invoice expiry into blinded path max_cltv_expiry Will be useful for static invoices' blinded paths, which may have long expiries. Rather than having a default max_cltv_expiry, we now base it on the invoice expiry. --- lightning/src/ln/channelmanager.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 4fae10cc66c..8a3d90f1786 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10077,7 +10077,7 @@ where Ok((payment_hash, payment_secret)) => { let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); let payment_paths = self.create_blinded_payment_paths( - Some(amount_msats), payment_secret, payment_context + Some(amount_msats), payment_secret, payment_context, relative_expiry, ) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -10384,7 +10384,8 @@ where /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. fn create_blinded_payment_paths( - &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext + &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, + relative_expiry_seconds: u32 ) -> Result, ()> { let expanded_key = &self.inbound_payment_key; let entropy = &*self.entropy_source; @@ -10392,8 +10393,13 @@ where let first_hops = self.list_usable_channels(); let payee_node_id = self.get_our_node_id(); - let max_cltv_expiry = self.best_block.read().unwrap().height + CLTV_FAR_FAR_AWAY - + LATENCY_GRACE_PERIOD_BLOCKS; + + // Assume shorter than usual block times to avoid spuriously failing payments too early. + const SECONDS_PER_BLOCK: u32 = 9 * 60; + let relative_expiry_blocks = relative_expiry_seconds / SECONDS_PER_BLOCK; + let max_cltv_expiry = core::cmp::max(relative_expiry_blocks, CLTV_FAR_FAR_AWAY) + .saturating_add(LATENCY_GRACE_PERIOD_BLOCKS) + .saturating_add(self.best_block.read().unwrap().height); let payee_tlvs = UnauthenticatedReceiveTlvs { payment_secret, @@ -11955,7 +11961,7 @@ where invoice_request: invoice_request.fields(), }); let payment_paths = match self.create_blinded_payment_paths( - Some(amount_msats), payment_secret, payment_context + Some(amount_msats), payment_secret, payment_context, relative_expiry ) { Ok(payment_paths) => payment_paths, Err(()) => { From d3f02ee51d3964cd05dd93a32f943a146d4be772 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 24 Jun 2024 15:51:27 -0400 Subject: [PATCH 08/23] Add PaymentContext for async payments This context is stored in the blinded payment paths we put in static invoices and is useful to authenticate payments over these paths to the recipient. We can't reuse Bolt12OfferContext for this because we don't have access to the invoice request fields at static invoice creation time. --- lightning/src/blinded_path/payment.rs | 22 ++++++++++++++++++++++ lightning/src/events/mod.rs | 22 ++++++++++++++-------- lightning/src/ln/channelmanager.rs | 9 +++++++-- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index e3a81927146..cf5af7d784e 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -349,6 +349,11 @@ pub enum PaymentContext { /// [`Offer`]: crate::offers::offer::Offer Bolt12Offer(Bolt12OfferContext), + /// The payment was made for a static invoice requested from a BOLT 12 [`Offer`]. + /// + /// [`Offer`]: crate::offers::offer::Offer + AsyncBolt12Offer(AsyncBolt12OfferContext), + /// The payment was made for an invoice sent for a BOLT 12 [`Refund`]. /// /// [`Refund`]: crate::offers::refund::Refund @@ -378,6 +383,18 @@ pub struct Bolt12OfferContext { pub invoice_request: InvoiceRequestFields, } +/// The context of a payment made for a static invoice requested from a BOLT 12 [`Offer`]. +/// +/// [`Offer`]: crate::offers::offer::Offer +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AsyncBolt12OfferContext { + /// The [`Nonce`] used to verify that an inbound [`InvoiceRequest`] corresponds to this static + /// invoice's offer. + /// + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + pub offer_nonce: Nonce, +} + /// The context of a payment made for an invoice sent for a BOLT 12 [`Refund`]. /// /// [`Refund`]: crate::offers::refund::Refund @@ -627,6 +644,7 @@ impl_writeable_tlv_based_enum_legacy!(PaymentContext, // 0 for Unknown removed in version 0.1. (1, Bolt12Offer), (2, Bolt12Refund), + (3, AsyncBolt12Offer), ); impl<'a> Writeable for PaymentContextRef<'a> { @@ -651,6 +669,10 @@ impl_writeable_tlv_based!(Bolt12OfferContext, { (2, invoice_request, required), }); +impl_writeable_tlv_based!(AsyncBolt12OfferContext, { + (0, offer_nonce, required), +}); + impl_writeable_tlv_based!(Bolt12RefundContext, {}); #[cfg(test)] diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index c5572822643..b3e05cfe4d8 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -181,27 +181,32 @@ impl PaymentPurpose { pub(crate) fn from_parts( payment_preimage: Option, payment_secret: PaymentSecret, payment_context: Option, - ) -> Self { + ) -> Result { match payment_context { None => { - PaymentPurpose::Bolt11InvoicePayment { + Ok(PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret, - } + }) }, Some(PaymentContext::Bolt12Offer(context)) => { - PaymentPurpose::Bolt12OfferPayment { + Ok(PaymentPurpose::Bolt12OfferPayment { payment_preimage, payment_secret, payment_context: context, - } + }) }, Some(PaymentContext::Bolt12Refund(context)) => { - PaymentPurpose::Bolt12RefundPayment { + Ok(PaymentPurpose::Bolt12RefundPayment { payment_preimage, payment_secret, payment_context: context, - } + }) + }, + Some(PaymentContext::AsyncBolt12Offer(_context)) => { + // This code will change to return Self::Bolt12OfferPayment when we add support for async + // receive. + Err(()) }, } } @@ -1867,7 +1872,8 @@ impl MaybeReadable for Event { (13, payment_id, option), }); let purpose = match payment_secret { - Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context), + Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context) + .map_err(|()| msgs::DecodeError::InvalidValue)?, None if payment_preimage.is_some() => PaymentPurpose::SpontaneousPayment(payment_preimage.unwrap()), None => return Err(msgs::DecodeError::InvalidValue), }; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8a3d90f1786..150c5d68959 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6209,11 +6209,16 @@ where match claimable_htlc.onion_payload { OnionPayload::Invoice { .. } => { let payment_data = payment_data.unwrap(); - let purpose = events::PaymentPurpose::from_parts( + let purpose = match events::PaymentPurpose::from_parts( payment_preimage, payment_data.payment_secret, payment_context, - ); + ) { + Ok(purpose) => purpose, + Err(()) => { + fail_htlc!(claimable_htlc, payment_hash); + }, + }; check_total_value!(purpose); }, OnionPayload::Spontaneous(preimage) => { From 8ad7f668039737e1c7ab33a87bc56544c58b5507 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 30 Oct 2024 14:20:50 -0400 Subject: [PATCH 09/23] Add onion message AsyncPaymentsContext for inbound payments This context is included in static invoice's blinded message paths, provided back to us in HeldHtlcAvailable onion messages for blinded path authentication. In future work, we will check if this context is valid and respond with a ReleaseHeldHtlc message to release the upstream payment if so. We also add creation methods for the hmac used for authenticating said blinded path. --- lightning/src/blinded_path/message.rs | 22 ++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 7 ++++++- lightning/src/offers/signer.rs | 18 ++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 4d96434dd63..51494e1b21e 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -402,6 +402,24 @@ pub enum AsyncPaymentsContext { /// containing the expected [`PaymentId`]. hmac: Hmac, }, + /// Context contained within the [`BlindedMessagePath`]s we put in static invoices, provided back + /// to us in corresponding [`HeldHtlcAvailable`] messages. + /// + /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable + InboundPayment { + /// A nonce used for authenticating that a [`HeldHtlcAvailable`] message is valid for a + /// preceding static invoice. + /// + /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable + nonce: Nonce, + /// Authentication code for the [`HeldHtlcAvailable`] message. + /// + /// Prevents nodes from creating their own blinded path to us, sending a [`HeldHtlcAvailable`] + /// message and trivially getting notified whenever we come online. + /// + /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable + hmac: Hmac, + }, } impl_writeable_tlv_based_enum!(MessageContext, @@ -433,6 +451,10 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext, (2, nonce, required), (4, hmac, required), }, + (1, InboundPayment) => { + (0, nonce, required), + (2, hmac, required), + }, ); /// Contains a simple nonce for use in a blinded path's context. diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 150c5d68959..eb7e9d5a28f 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12116,7 +12116,12 @@ where fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { #[cfg(async_payments)] { - let AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } = _context; + let (payment_id, nonce, hmac) = match _context { + AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } => { + (payment_id, nonce, hmac) + }, + _ => return + }; if payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key).is_err() { return } if let Err(e) = self.send_payment_for_static_invoice(payment_id) { log_trace!( diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index fa9fdfa3467..7deff734b34 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -50,6 +50,11 @@ const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16]; // HMAC input for `ReceiveTlvs`. The HMAC is used in `blinded_path::payment::PaymentContext`. const PAYMENT_TLVS_HMAC_INPUT: &[u8; 16] = &[8; 16]; +// HMAC input used in `AsyncPaymentsContext::InboundPayment` to authenticate inbound +// held_htlc_available onion messages. +#[cfg(async_payments)] +const ASYNC_PAYMENTS_HELD_HTLC_HMAC_INPUT: &[u8; 16] = &[9; 16]; + /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified. #[derive(Clone)] @@ -483,3 +488,16 @@ pub(crate) fn verify_payment_tlvs( ) -> Result<(), ()> { if hmac_for_payment_tlvs(receive_tlvs, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) } } + +#[cfg(async_payments)] +pub(crate) fn hmac_for_held_htlc_available_context( + nonce: Nonce, expanded_key: &ExpandedKey, +) -> Hmac { + const IV_BYTES: &[u8; IV_LEN] = b"LDK Held HTLC OM"; + let mut hmac = expanded_key.hmac_for_offer(); + hmac.input(IV_BYTES); + hmac.input(&nonce.0); + hmac.input(ASYNC_PAYMENTS_HELD_HTLC_HMAC_INPUT); + + Hmac::from_engine(hmac) +} From 5e172f85807e3473961f169816b9408023784688 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 30 Oct 2024 16:09:25 -0400 Subject: [PATCH 10/23] Add utils to create static invoices and their corresponding offers We can't use our regular offer creation util for receiving async payments because the recipient can't be relied on to be online to service invoice_requests. Therefore, add a new offer creation util that is parameterized by blinded message paths to another node on the network that *is* always-online and can serve static invoices on behalf of the often-offline recipient. Also add a utility for creating static invoices corresponding to these offers. See new utils' docs and BOLTs PR 1149 for more info. --- lightning/src/ln/channelmanager.rs | 87 +++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index eb7e9d5a28f..f402cd2073a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -74,8 +74,6 @@ use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; use crate::offers::signer; -#[cfg(async_payments)] -use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler}; use crate::onion_message::dns_resolution::HumanReadableName; use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions}; @@ -90,6 +88,11 @@ use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, Maybe use crate::util::ser::TransactionU16LenLimited; use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; +#[cfg(async_payments)] use { + crate::blinded_path::payment::AsyncBolt12OfferContext, + crate::offers::offer::Amount, + crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder}, +}; #[cfg(feature = "dnssec")] use crate::blinded_path::message::DNSResolverContext; @@ -9883,6 +9886,86 @@ where #[cfg(c_bindings)] create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder); + /// Create an offer for receiving async payments as an often-offline recipient. + /// + /// Because we may be offline when the payer attempts to request an invoice, you MUST: + /// 1. Provide at least 1 [`BlindedMessagePath`] terminating at an always-online node that will + /// serve the [`StaticInvoice`] created from this offer on our behalf. + /// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this + /// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the + /// aforementioned always-online node. + #[cfg(async_payments)] + pub fn create_async_receive_offer_builder( + &self, message_paths_to_always_online_node: Vec + ) -> Result<(OfferBuilder, Nonce), Bolt12SemanticError> { + if message_paths_to_always_online_node.is_empty() { + return Err(Bolt12SemanticError::MissingPaths) + } + + let node_id = self.get_our_node_id(); + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let nonce = Nonce::from_entropy_source(entropy); + let mut builder = OfferBuilder::deriving_signing_pubkey( + node_id, expanded_key, nonce, secp_ctx + ).chain_hash(self.chain_hash); + + for path in message_paths_to_always_online_node { + builder = builder.path(path); + } + + Ok((builder.into(), nonce)) + } + + /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were + /// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the + /// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`]. + #[cfg(async_payments)] + pub fn create_static_invoice_builder<'a>( + &self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option + ) -> Result, Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let payment_context = PaymentContext::AsyncBolt12Offer( + AsyncBolt12OfferContext { offer_nonce } + ); + let amount_msat = offer.amount().and_then(|amount| { + match amount { + Amount::Bitcoin { amount_msats } => Some(amount_msats), + Amount::Currency { .. } => None + } + }); + + let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY); + let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX); + + let created_at = self.duration_since_epoch(); + let payment_secret = inbound_payment::create_for_spontaneous_payment( + &self.inbound_payment_key, amount_msat, relative_expiry_secs, created_at.as_secs(), None + ).map_err(|()| Bolt12SemanticError::InvalidAmount)?; + + let payment_paths = self.create_blinded_payment_paths( + amount_msat, payment_secret, payment_context, relative_expiry_secs + ).map_err(|()| Bolt12SemanticError::MissingPaths)?; + + let nonce = Nonce::from_entropy_source(entropy); + let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key); + let context = MessageContext::AsyncPayments( + AsyncPaymentsContext::InboundPayment { nonce, hmac } + ); + let async_receive_message_paths = self.create_blinded_paths(context) + .map_err(|()| Bolt12SemanticError::MissingPaths)?; + + StaticInvoiceBuilder::for_offer_using_derived_keys( + offer, payment_paths, async_receive_message_paths, created_at, expanded_key, + offer_nonce, secp_ctx + ).map(|inv| inv.allow_mpp().relative_expiry(relative_expiry_secs)) + } + /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual /// [`Bolt12Invoice`] once it is received. From 24350eea641f8eab6be18bc62d3aee7b72a2518e Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 13 Nov 2024 20:25:42 -0500 Subject: [PATCH 11/23] Test failures on paying static invoices Since adding support for creating static invoices from ChannelManager, it's easier to test these failure cases that went untested when we added support for paying static invoices. --- lightning/src/ln/async_payments_tests.rs | 359 +++++++++++++++++++++++ lightning/src/ln/mod.rs | 3 + lightning/src/ln/offers_tests.rs | 2 +- 3 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 lightning/src/ln/async_payments_tests.rs diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs new file mode 100644 index 00000000000..5444ccca969 --- /dev/null +++ b/lightning/src/ln/async_payments_tests.rs @@ -0,0 +1,359 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use crate::blinded_path::message::{MessageContext, OffersContext}; +use crate::events::{Event, MessageSendEventsProvider, PaymentFailureReason}; +use crate::ln::channelmanager::PaymentId; +use crate::ln::functional_test_utils::*; +use crate::ln::msgs::OnionMessageHandler; +use crate::ln::offers_tests; +use crate::ln::outbound_payment::Retry; +use crate::offers::nonce::Nonce; +use crate::onion_message::async_payments::{ + AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc, +}; +use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions}; +use crate::onion_message::offers::OffersMessage; +use crate::onion_message::packet::ParsedOnionMessageContents; +use crate::prelude::*; +use crate::types::features::Bolt12InvoiceFeatures; +use bitcoin::secp256k1::Secp256k1; + +use core::convert::Infallible; +use core::time::Duration; + +#[test] +#[cfg(async_payments)] +fn static_invoice_unknown_required_features() { + // Test that we will fail to pay a static invoice with unsupported required features. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + let blinded_paths_to_always_online_node = nodes[1] + .message_router + .create_blinded_paths( + nodes[1].node.get_our_node_id(), + MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), + Vec::new(), + &secp_ctx, + ) + .unwrap(); + let (offer_builder, nonce) = nodes[2] + .node + .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .unwrap(); + let offer = offer_builder.build().unwrap(); + let static_invoice_unknown_req_features = nodes[2] + .node + .create_static_invoice_builder(&offer, nonce, None) + .unwrap() + .features_unchecked(Bolt12InvoiceFeatures::unknown()) + .build_and_sign(&secp_ctx) + .unwrap(); + + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) + .unwrap(); + + // Don't forward the invreq since we don't support retrieving the static invoice from the + // recipient's LSP yet, instead manually construct the response. + let invreq_om = nodes[0] + .onion_messenger + .next_onion_message_for_peer(nodes[1].node.get_our_node_id()) + .unwrap(); + let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1; + nodes[1] + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + static_invoice_unknown_req_features, + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path), + }, + ) + .unwrap(); + + let static_invoice_om = nodes[1] + .onion_messenger + .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) + .unwrap(); + nodes[0] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om); + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::PaymentFailed { payment_hash, payment_id: ev_payment_id, reason } => { + assert_eq!(payment_hash, None); + assert_eq!(payment_id, ev_payment_id); + assert_eq!(reason, Some(PaymentFailureReason::UnknownRequiredFeatures)); + }, + _ => panic!(), + } +} + +#[test] +fn ignore_unexpected_static_invoice() { + // Test that we'll ignore unexpected static invoices, invoices that don't match our invoice + // request, and duplicate invoices. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + // Initiate payment to the sender's intended offer. + let blinded_paths_to_always_online_node = nodes[1] + .message_router + .create_blinded_paths( + nodes[1].node.get_our_node_id(), + MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), + Vec::new(), + &secp_ctx, + ) + .unwrap(); + let (offer_builder, offer_nonce) = nodes[2] + .node + .create_async_receive_offer_builder(blinded_paths_to_always_online_node.clone()) + .unwrap(); + let offer = offer_builder.build().unwrap(); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) + .unwrap(); + + // Don't forward the invreq since we don't support retrieving the static invoice from the + // recipient's LSP yet, instead manually construct the responses below. + let invreq_om = nodes[0] + .onion_messenger + .next_onion_message_for_peer(nodes[1].node.get_our_node_id()) + .unwrap(); + let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1; + + // Create a static invoice to be sent over the reply path containing the original payment_id, but + // the static invoice corresponds to a different offer than was originally paid. + let unexpected_static_invoice = { + let (offer_builder, nonce) = nodes[2] + .node + .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .unwrap(); + let sender_unintended_offer = offer_builder.build().unwrap(); + + nodes[2] + .node + .create_static_invoice_builder(&sender_unintended_offer, nonce, None) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap() + }; + + // Check that we'll ignore the unexpected static invoice. + nodes[1] + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + unexpected_static_invoice, + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path.clone()), + }, + ) + .unwrap(); + let unexpected_static_invoice_om = nodes[1] + .onion_messenger + .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) + .unwrap(); + nodes[0] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &unexpected_static_invoice_om); + let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node); + assert!(async_pmts_msgs.is_empty()); + assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); + + // A valid static invoice corresponding to the correct offer will succeed and cause us to send a + // held_htlc_available onion message. + let valid_static_invoice = nodes[2] + .node + .create_static_invoice_builder(&offer, offer_nonce, None) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + + nodes[1] + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + valid_static_invoice.clone(), + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path.clone()), + }, + ) + .unwrap(); + let static_invoice_om = nodes[1] + .onion_messenger + .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) + .unwrap(); + nodes[0] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om); + let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node); + assert!(!async_pmts_msgs.is_empty()); + assert!(async_pmts_msgs + .into_iter() + .all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_)))); + + // Receiving a duplicate invoice will have no effect. + nodes[1] + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + valid_static_invoice, + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path), + }, + ) + .unwrap(); + let dup_static_invoice_om = nodes[1] + .onion_messenger + .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) + .unwrap(); + nodes[0] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &dup_static_invoice_om); + let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node); + assert!(async_pmts_msgs.is_empty()); +} + +#[test] +fn pays_static_invoice() { + // Test that we support the async payments flow up to and including sending the actual payment. + // Async receive is not yet supported so we don't complete the payment yet. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + let blinded_paths_to_always_online_node = nodes[1] + .message_router + .create_blinded_paths( + nodes[1].node.get_our_node_id(), + MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), + Vec::new(), + &secp_ctx, + ) + .unwrap(); + let (offer_builder, offer_nonce) = nodes[2] + .node + .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .unwrap(); + let offer = offer_builder.build().unwrap(); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + let relative_expiry = Duration::from_secs(1000); + let static_invoice = nodes[2] + .node + .create_static_invoice_builder(&offer, offer_nonce, Some(relative_expiry)) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + assert!(static_invoice.invoice_features().supports_basic_mpp()); + assert_eq!(static_invoice.relative_expiry(), relative_expiry); + + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) + .unwrap(); + + // Don't forward the invreq since we don't support retrieving the static invoice from the + // recipient's LSP yet, instead manually construct the response. + let invreq_om = nodes[0] + .onion_messenger + .next_onion_message_for_peer(nodes[1].node.get_our_node_id()) + .unwrap(); + let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1; + + nodes[1] + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + static_invoice, + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path), + }, + ) + .unwrap(); + let static_invoice_om = nodes[1] + .onion_messenger + .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) + .unwrap(); + nodes[0] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om); + let mut async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node); + assert!(!async_pmts_msgs.is_empty()); + assert!(async_pmts_msgs + .iter() + .all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_)))); + + // Manually send the message and context releasing the HTLC since the recipient doesn't support + // responding themselves yet. + let held_htlc_avail_reply_path = match async_pmts_msgs.pop().unwrap().1 { + MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => reply_path, + _ => panic!(), + }; + nodes[2] + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::AsyncPayments( + AsyncPaymentsMessage::ReleaseHeldHtlc(ReleaseHeldHtlc {}), + ), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(held_htlc_avail_reply_path), + }, + ) + .unwrap(); + + let release_held_htlc_om = nodes[2] + .onion_messenger + .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) + .unwrap(); + nodes[0] + .onion_messenger + .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); + + // Check that we've queued the HTLCs of the async keysend payment. + let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); + assert_eq!(htlc_updates.update_add_htlcs.len(), 1); + check_added_monitors!(nodes[0], 1); + + // Receiving a duplicate release_htlc message doesn't result in duplicate payment. + nodes[0] + .onion_messenger + .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); + assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); +} diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 1f6a1096e11..201f2ecfeff 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -55,6 +55,9 @@ pub use onion_utils::create_payment_onion; #[cfg(test)] #[allow(unused_mut)] mod blinded_payment_tests; +#[cfg(all(test, async_payments))] +#[allow(unused_mut)] +mod async_payments_tests; #[cfg(test)] #[allow(unused_mut)] mod functional_tests; diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 6455a60b139..2cbf9e56647 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -199,7 +199,7 @@ fn extract_offer_nonce<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessa } } -fn extract_invoice_request<'a, 'b, 'c>( +pub(super) fn extract_invoice_request<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, message: &OnionMessage ) -> (InvoiceRequest, BlindedMessagePath) { match node.onion_messenger.peel_onion_message(message) { From 94ef25a6979599c7bb76b271fde2395023a19e20 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 3 Jan 2025 17:55:41 -0500 Subject: [PATCH 12/23] Move blinded keysend tests into new async_payments_tests.rs Blinded keysend is only planned to be supported in the async payments context. --- lightning/src/ln/async_payments_tests.rs | 248 +++++++++++++++++++++- lightning/src/ln/blinded_payment_tests.rs | 149 +------------ 2 files changed, 246 insertions(+), 151 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 5444ccca969..424d76da6c2 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -8,11 +8,16 @@ // licenses. use crate::blinded_path::message::{MessageContext, OffersContext}; -use crate::events::{Event, MessageSendEventsProvider, PaymentFailureReason}; -use crate::ln::channelmanager::PaymentId; +use crate::events::{Event, HTLCDestination, MessageSendEventsProvider, PaymentFailureReason}; +use crate::ln::blinded_payment_tests::{blinded_payment_path, get_blinded_route_parameters}; +use crate::ln::channelmanager; +use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; +use crate::ln::inbound_payment; +use crate::ln::msgs::ChannelMessageHandler; use crate::ln::msgs::OnionMessageHandler; use crate::ln::offers_tests; +use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::outbound_payment::Retry; use crate::offers::nonce::Nonce; use crate::onion_message::async_payments::{ @@ -22,14 +27,251 @@ use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendIns use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::prelude::*; +use crate::routing::router::{PaymentParameters, RouteParameters}; +use crate::sign::NodeSigner; use crate::types::features::Bolt12InvoiceFeatures; +use crate::types::payment::{PaymentPreimage, PaymentSecret}; +use crate::util::config::UserConfig; use bitcoin::secp256k1::Secp256k1; use core::convert::Infallible; use core::time::Duration; #[test] -#[cfg(async_payments)] +fn blinded_keysend() { + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_upd_1_2 = + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + + let inbound_payment_key = nodes[2].keys_manager.get_inbound_payment_key(); + let payment_secret = inbound_payment::create_for_spontaneous_payment( + &inbound_payment_key, + None, + u32::MAX, + nodes[2].node.duration_since_epoch().as_secs(), + None, + ) + .unwrap(); + + let amt_msat = 5000; + let keysend_preimage = PaymentPreimage([42; 32]); + let route_params = get_blinded_route_parameters( + amt_msat, + payment_secret, + 1, + 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), + &[&chan_upd_1_2], + &chanmon_cfgs[2].keys_manager, + ); + + let payment_hash = nodes[0] + .node + .send_spontaneous_payment( + Some(keysend_preimage), + RecipientOnionFields::spontaneous_empty(), + PaymentId(keysend_preimage.0), + route_params, + Retry::Attempts(0), + ) + .unwrap(); + check_added_monitors(&nodes[0], 1); + + let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + pass_along_path( + &nodes[0], + expected_route[0], + amt_msat, + payment_hash, + Some(payment_secret), + ev.clone(), + true, + Some(keysend_preimage), + ); + claim_payment_along_route(ClaimAlongRouteArgs::new( + &nodes[0], + expected_route, + keysend_preimage, + )); +} + +#[test] +fn blinded_mpp_keysend() { + let chanmon_cfgs = create_chanmon_cfgs(4); + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]); + let nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes(&nodes, 0, 1); + create_announced_chan_between_nodes(&nodes, 0, 2); + let chan_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3); + let chan_2_3 = create_announced_chan_between_nodes(&nodes, 2, 3); + + let inbound_payment_key = nodes[3].keys_manager.get_inbound_payment_key(); + let payment_secret = inbound_payment::create_for_spontaneous_payment( + &inbound_payment_key, + None, + u32::MAX, + nodes[3].node.duration_since_epoch().as_secs(), + None, + ) + .unwrap(); + + let amt_msat = 15_000_000; + let keysend_preimage = PaymentPreimage([42; 32]); + let route_params = { + let pay_params = PaymentParameters::blinded(vec![ + blinded_payment_path( + payment_secret, + 1, + 1_0000_0000, + vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], + &[&chan_1_3.0.contents], + &chanmon_cfgs[3].keys_manager, + ), + blinded_payment_path( + payment_secret, + 1, + 1_0000_0000, + vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], + &[&chan_2_3.0.contents], + &chanmon_cfgs[3].keys_manager, + ), + ]) + .with_bolt12_features(channelmanager::provided_bolt12_invoice_features( + &UserConfig::default(), + )) + .unwrap(); + RouteParameters::from_payment_params_and_value(pay_params, amt_msat) + }; + + let payment_hash = nodes[0] + .node + .send_spontaneous_payment( + Some(keysend_preimage), + RecipientOnionFields::spontaneous_empty(), + PaymentId(keysend_preimage.0), + route_params, + Retry::Attempts(0), + ) + .unwrap(); + check_added_monitors!(nodes[0], 2); + + let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 2); + + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + pass_along_path( + &nodes[0], + expected_route[0], + amt_msat, + payment_hash.clone(), + Some(payment_secret), + ev.clone(), + false, + Some(keysend_preimage), + ); + + let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); + pass_along_path( + &nodes[0], + expected_route[1], + amt_msat, + payment_hash.clone(), + Some(payment_secret), + ev.clone(), + true, + Some(keysend_preimage), + ); + claim_payment_along_route(ClaimAlongRouteArgs::new( + &nodes[0], + expected_route, + keysend_preimage, + )); +} + +#[test] +fn invalid_keysend_payment_secret() { + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_upd_1_2 = + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + + let invalid_payment_secret = PaymentSecret([42; 32]); + let amt_msat = 5000; + let keysend_preimage = PaymentPreimage([42; 32]); + let route_params = get_blinded_route_parameters( + amt_msat, + invalid_payment_secret, + 1, + 1_0000_0000, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), + &[&chan_upd_1_2], + &chanmon_cfgs[2].keys_manager, + ); + + let payment_hash = nodes[0] + .node + .send_spontaneous_payment( + Some(keysend_preimage), + RecipientOnionFields::spontaneous_empty(), + PaymentId(keysend_preimage.0), + route_params, + Retry::Attempts(0), + ) + .unwrap(); + check_added_monitors(&nodes[0], 1); + + let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let args = + PassAlongPathArgs::new(&nodes[0], &expected_route[0], amt_msat, payment_hash, ev.clone()) + .with_payment_secret(invalid_payment_secret) + .with_payment_preimage(keysend_preimage) + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + + let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1); + let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + nodes[1] + .node + .handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false); + + let updates_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + assert_eq!(updates_1_0.update_fail_htlcs.len(), 1); + nodes[0].node.handle_update_fail_htlc( + nodes[1].node.get_our_node_id(), + &updates_1_0.update_fail_htlcs[0], + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false); + expect_payment_failed_conditions( + &nodes[0], + payment_hash, + false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]), + ); +} + +#[test] fn static_invoice_unknown_required_features() { // Test that we will fail to pay a static invoice with unsupported required features. let secp_ctx = Secp256k1::new(); diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index d27a7c8a235..813ee1dcf07 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -36,12 +36,8 @@ use crate::util::config::UserConfig; use crate::util::ser::WithoutLength; use crate::util::test_utils; use lightning_invoice::RawBolt11Invoice; -#[cfg(async_payments)] use { - crate::ln::inbound_payment, - crate::types::payment::PaymentPreimage, -}; -fn blinded_payment_path( +pub fn blinded_payment_path( payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64, node_ids: Vec, channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface @@ -1270,149 +1266,6 @@ fn conditionally_round_fwd_amt() { expect_payment_sent(&nodes[0], payment_preimage, Some(Some(expected_fee)), true, true); } -#[test] -#[cfg(async_payments)] -fn blinded_keysend() { - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); - create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; - - let inbound_payment_key = nodes[2].keys_manager.get_inbound_payment_key(); - let payment_secret = inbound_payment::create_for_spontaneous_payment( - &inbound_payment_key, None, u32::MAX, nodes[2].node.duration_since_epoch().as_secs(), None - ).unwrap(); - - let amt_msat = 5000; - let keysend_preimage = PaymentPreimage([42; 32]); - let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, - 1_0000_0000, - nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), - &[&chan_upd_1_2], &chanmon_cfgs[2].keys_manager); - - let payment_hash = nodes[0].node.send_spontaneous_payment(Some(keysend_preimage), RecipientOnionFields::spontaneous_empty(), PaymentId(keysend_preimage.0), route_params, Retry::Attempts(0)).unwrap(); - check_added_monitors(&nodes[0], 1); - - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - - let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); - pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash, Some(payment_secret), ev.clone(), true, Some(keysend_preimage)); - claim_payment_along_route( - ClaimAlongRouteArgs::new(&nodes[0], expected_route, keysend_preimage) - ); -} - -#[test] -#[cfg(async_payments)] -fn blinded_mpp_keysend() { - let chanmon_cfgs = create_chanmon_cfgs(4); - let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]); - let nodes = create_network(4, &node_cfgs, &node_chanmgrs); - - create_announced_chan_between_nodes(&nodes, 0, 1); - create_announced_chan_between_nodes(&nodes, 0, 2); - let chan_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3); - let chan_2_3 = create_announced_chan_between_nodes(&nodes, 2, 3); - - let inbound_payment_key = nodes[3].keys_manager.get_inbound_payment_key(); - let payment_secret = inbound_payment::create_for_spontaneous_payment( - &inbound_payment_key, None, u32::MAX, nodes[3].node.duration_since_epoch().as_secs(), None - ).unwrap(); - - let amt_msat = 15_000_000; - let keysend_preimage = PaymentPreimage([42; 32]); - let route_params = { - let pay_params = PaymentParameters::blinded( - vec![ - blinded_payment_path(payment_secret, 1, 1_0000_0000, - vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_1_3.0.contents], - &chanmon_cfgs[3].keys_manager - ), - blinded_payment_path(payment_secret, 1, 1_0000_0000, - vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], &[&chan_2_3.0.contents], - &chanmon_cfgs[3].keys_manager - ), - ] - ) - .with_bolt12_features(channelmanager::provided_bolt12_invoice_features(&UserConfig::default())) - .unwrap(); - RouteParameters::from_payment_params_and_value(pay_params, amt_msat) - }; - - let payment_hash = nodes[0].node.send_spontaneous_payment(Some(keysend_preimage), RecipientOnionFields::spontaneous_empty(), PaymentId(keysend_preimage.0), route_params, Retry::Attempts(0)).unwrap(); - check_added_monitors!(nodes[0], 2); - - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 2); - - let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); - pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash.clone(), - Some(payment_secret), ev.clone(), false, Some(keysend_preimage)); - - let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); - pass_along_path(&nodes[0], expected_route[1], amt_msat, payment_hash.clone(), - Some(payment_secret), ev.clone(), true, Some(keysend_preimage)); - claim_payment_along_route( - ClaimAlongRouteArgs::new(&nodes[0], expected_route, keysend_preimage) - ); -} - -#[test] -#[cfg(async_payments)] -fn invalid_keysend_payment_secret() { - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); - create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; - - let invalid_payment_secret = PaymentSecret([42; 32]); - let amt_msat = 5000; - let keysend_preimage = PaymentPreimage([42; 32]); - let route_params = get_blinded_route_parameters( - amt_msat, invalid_payment_secret, 1, 1_0000_0000, - nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], - &chanmon_cfgs[2].keys_manager - ); - - let payment_hash = nodes[0].node.send_spontaneous_payment(Some(keysend_preimage), RecipientOnionFields::spontaneous_empty(), PaymentId(keysend_preimage.0), route_params, Retry::Attempts(0)).unwrap(); - check_added_monitors(&nodes[0], 1); - - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - - let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); - let args = PassAlongPathArgs::new( - &nodes[0], &expected_route[0], amt_msat, payment_hash, ev.clone() - ) - .with_payment_secret(invalid_payment_secret) - .with_payment_preimage(keysend_preimage) - .expect_failure(HTLCDestination::FailedPayment { payment_hash }); - do_pass_along_path(args); - - let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); - assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1); - let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0]; - assert_eq!(update_malformed.sha256_of_onion, [0; 32]); - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); - nodes[1].node.handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed); - do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false); - - let updates_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); - assert_eq!(updates_1_0.update_fail_htlcs.len(), 1); - nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]); - do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false); - expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); -} #[test] fn custom_tlvs_to_blinded_path() { From a149c3630a62489ed722e38937403edcdcaaf246 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 6 Jan 2025 14:10:57 -0500 Subject: [PATCH 13/23] Tests: DRY failing a blinded HTLC backwards Blinded HTLCs are always failed back with the same error, so DRY the test code that fails them backwards. This util will also be used for async payments testing in upcoming commits. --- lightning/src/ln/blinded_payment_tests.rs | 70 +++++++++++------------ 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 813ee1dcf07..9708dbd6d88 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -105,6 +105,37 @@ pub fn get_blinded_route_parameters( ) } +pub fn fail_blinded_htlc_backwards( + payment_hash: PaymentHash, intro_node_idx: usize, nodes: &[&Node], +) { + for i in (0..nodes.len()).rev() { + match i { + 0 => { + let mut payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + }, + i if i <= intro_node_idx => { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[i], nodes[i-1].node.get_our_node_id()); + assert_eq!(unblinded_node_updates.update_fail_htlcs.len(), 1); + nodes[i-1].node.handle_update_fail_htlc( + nodes[i].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[i-1] + ); + do_commitment_signed_dance(&nodes[i-1], &nodes[i], &unblinded_node_updates.commitment_signed, false, false); + }, + _ => { + let blinded_node_updates = get_htlc_update_msgs!(nodes[i], nodes[i-1].node.get_our_node_id()); + assert_eq!(blinded_node_updates.update_fail_malformed_htlcs.len(), 1); + let update_malformed = &blinded_node_updates.update_fail_malformed_htlcs[0]; + assert_eq!(update_malformed.sha256_of_onion, [0; 32]); + assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); + nodes[i-1].node.handle_update_fail_malformed_htlc(nodes[i].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[i-1], &nodes[i], &blinded_node_updates.commitment_signed, true, false); + } + } + } +} + #[test] fn one_hop_blinded_path() { do_one_hop_blinded_path(true); @@ -572,13 +603,8 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, if intro_fails { cause_error!(nodes[0], nodes[1], nodes[2], chan_id_1_2, chan_upd_1_2.short_channel_id); - let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); - nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); check_added_monitors!(nodes[1], 1); - do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); - - expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]]); return } @@ -669,14 +695,8 @@ fn do_blinded_intercept_payment(intercept_node_fails: bool) { nodes[1].node.fail_intercepted_htlc(intercept_id).unwrap(); expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]); nodes[1].node.process_pending_htlc_forwards(); - let update_fail = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); check_added_monitors!(&nodes[1], 1); - assert!(update_fail.update_fail_htlcs.len() == 1); - let fail_msg = update_fail.update_fail_htlcs[0].clone(); - nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &fail_msg); - commitment_signed_dance!(nodes[0], nodes[1], update_fail.commitment_signed, false); - expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]]); return } @@ -782,29 +802,7 @@ fn three_hop_blinded_path_fail() { ); nodes[3].node.process_pending_htlc_forwards(); check_added_monitors!(nodes[3], 1); - - let updates_3_2 = get_htlc_update_msgs!(nodes[3], nodes[2].node.get_our_node_id()); - assert_eq!(updates_3_2.update_fail_malformed_htlcs.len(), 1); - let update_malformed = &updates_3_2.update_fail_malformed_htlcs[0]; - assert_eq!(update_malformed.sha256_of_onion, [0; 32]); - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); - nodes[2].node.handle_update_fail_malformed_htlc(nodes[3].node.get_our_node_id(), update_malformed); - do_commitment_signed_dance(&nodes[2], &nodes[3], &updates_3_2.commitment_signed, true, false); - - let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); - assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1); - let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0]; - assert_eq!(update_malformed.sha256_of_onion, [0; 32]); - assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING); - nodes[1].node.handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed); - do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false); - - let updates_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); - assert_eq!(updates_1_0.update_fail_htlcs.len(), 1); - nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]); - do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false); - expect_payment_failed_conditions(&nodes[0], payment_hash, false, - PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2], &nodes[3]]); } #[derive(PartialEq)] From bea56e31219c588bddafd9b5ae970a5ee6549c9b Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 7 Jan 2025 23:36:25 -0500 Subject: [PATCH 14/23] Tests: DRY static invoice creation --- lightning/src/ln/async_payments_tests.rs | 94 ++++++++++-------------- 1 file changed, 37 insertions(+), 57 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 424d76da6c2..01135f625cd 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -20,6 +20,8 @@ use crate::ln::offers_tests; use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::outbound_payment::Retry; use crate::offers::nonce::Nonce; +use crate::offers::offer::Offer; +use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{ AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc, }; @@ -32,11 +34,39 @@ use crate::sign::NodeSigner; use crate::types::features::Bolt12InvoiceFeatures; use crate::types::payment::{PaymentPreimage, PaymentSecret}; use crate::util::config::UserConfig; +use bitcoin::secp256k1; use bitcoin::secp256k1::Secp256k1; use core::convert::Infallible; use core::time::Duration; +fn create_static_invoice( + always_online_counterparty: &Node, recipient: &Node, relative_expiry: Option, + secp_ctx: &Secp256k1, +) -> (Offer, StaticInvoice) { + let blinded_paths_to_always_online_node = always_online_counterparty + .message_router + .create_blinded_paths( + always_online_counterparty.node.get_our_node_id(), + MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), + Vec::new(), + &secp_ctx, + ) + .unwrap(); + let (offer_builder, offer_nonce) = recipient + .node + .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .unwrap(); + let offer = offer_builder.build().unwrap(); + let static_invoice = recipient + .node + .create_static_invoice_builder(&offer, offer_nonce, relative_expiry) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + (offer, static_invoice) +} + #[test] fn blinded_keysend() { let chanmon_cfgs = create_chanmon_cfgs(3); @@ -362,20 +392,8 @@ fn ignore_unexpected_static_invoice() { create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); // Initiate payment to the sender's intended offer. - let blinded_paths_to_always_online_node = nodes[1] - .message_router - .create_blinded_paths( - nodes[1].node.get_our_node_id(), - MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), - Vec::new(), - &secp_ctx, - ) - .unwrap(); - let (offer_builder, offer_nonce) = nodes[2] - .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node.clone()) - .unwrap(); - let offer = offer_builder.build().unwrap(); + let (offer, valid_static_invoice) = + create_static_invoice(&nodes[1], &nodes[2], None, &secp_ctx); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); nodes[0] @@ -393,20 +411,7 @@ fn ignore_unexpected_static_invoice() { // Create a static invoice to be sent over the reply path containing the original payment_id, but // the static invoice corresponds to a different offer than was originally paid. - let unexpected_static_invoice = { - let (offer_builder, nonce) = nodes[2] - .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) - .unwrap(); - let sender_unintended_offer = offer_builder.build().unwrap(); - - nodes[2] - .node - .create_static_invoice_builder(&sender_unintended_offer, nonce, None) - .unwrap() - .build_and_sign(&secp_ctx) - .unwrap() - }; + let unexpected_static_invoice = create_static_invoice(&nodes[1], &nodes[2], None, &secp_ctx).1; // Check that we'll ignore the unexpected static invoice. nodes[1] @@ -433,13 +438,6 @@ fn ignore_unexpected_static_invoice() { // A valid static invoice corresponding to the correct offer will succeed and cause us to send a // held_htlc_available onion message. - let valid_static_invoice = nodes[2] - .node - .create_static_invoice_builder(&offer, offer_nonce, None) - .unwrap() - .build_and_sign(&secp_ctx) - .unwrap(); - nodes[1] .onion_messenger .send_onion_message( @@ -499,32 +497,14 @@ fn pays_static_invoice() { create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); - let blinded_paths_to_always_online_node = nodes[1] - .message_router - .create_blinded_paths( - nodes[1].node.get_our_node_id(), - MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), - Vec::new(), - &secp_ctx, - ) - .unwrap(); - let (offer_builder, offer_nonce) = nodes[2] - .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) - .unwrap(); - let offer = offer_builder.build().unwrap(); - let amt_msat = 5000; - let payment_id = PaymentId([1; 32]); let relative_expiry = Duration::from_secs(1000); - let static_invoice = nodes[2] - .node - .create_static_invoice_builder(&offer, offer_nonce, Some(relative_expiry)) - .unwrap() - .build_and_sign(&secp_ctx) - .unwrap(); + let (offer, static_invoice) = + create_static_invoice(&nodes[1], &nodes[2], Some(relative_expiry), &secp_ctx); assert!(static_invoice.invoice_features().supports_basic_mpp()); assert_eq!(static_invoice.relative_expiry(), relative_expiry); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); nodes[0] .node .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) From 53866bfb691a56039385c376dd6072b8a5475e73 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 4 Dec 2024 14:00:22 -0500 Subject: [PATCH 15/23] Add handle_held_htlc_available MessageContext param Needed to authenticate that the held_htlc_available message is being sent over a reply path that we originally created and that isn't expired before we reply with release_held_htlc. This context will be used in upcoming commits when we add support for async receive. --- fuzz/src/onion_message.rs | 3 ++- lightning/src/ln/channelmanager.rs | 3 ++- lightning/src/ln/peer_handler.rs | 3 ++- lightning/src/onion_message/async_payments.rs | 3 ++- lightning/src/onion_message/functional_tests.rs | 3 ++- lightning/src/onion_message/messenger.rs | 10 +++++++++- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 94da4d09be5..10a667fb594 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -122,7 +122,8 @@ struct TestAsyncPaymentsMessageHandler {} impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler { fn handle_held_htlc_available( - &self, _message: HeldHtlcAvailable, responder: Option, + &self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext, + responder: Option, ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { let responder = match responder { Some(resp) => resp, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f402cd2073a..b9764a0beaf 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12192,7 +12192,8 @@ where L::Target: Logger, { fn handle_held_htlc_available( - &self, _message: HeldHtlcAvailable, _responder: Option + &self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext, + _responder: Option ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { None } diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index dbce9ca0498..80b92cec1bd 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -149,7 +149,8 @@ impl OffersMessageHandler for IgnoringMessageHandler { } impl AsyncPaymentsMessageHandler for IgnoringMessageHandler { fn handle_held_htlc_available( - &self, _message: HeldHtlcAvailable, _responder: Option, + &self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext, + _responder: Option, ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { None } diff --git a/lightning/src/onion_message/async_payments.rs b/lightning/src/onion_message/async_payments.rs index d81010e5d5f..7a473c90e8f 100644 --- a/lightning/src/onion_message/async_payments.rs +++ b/lightning/src/onion_message/async_payments.rs @@ -28,7 +28,8 @@ pub trait AsyncPaymentsMessageHandler { /// Handle a [`HeldHtlcAvailable`] message. A [`ReleaseHeldHtlc`] should be returned to release /// the held funds. fn handle_held_htlc_available( - &self, message: HeldHtlcAvailable, responder: Option, + &self, message: HeldHtlcAvailable, context: AsyncPaymentsContext, + responder: Option, ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)>; /// Handle a [`ReleaseHeldHtlc`] message. If authentication of the message succeeds, an HTLC diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index f9d73f05ff3..57a7bc8afd5 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -85,7 +85,8 @@ struct TestAsyncPaymentsMessageHandler {} impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler { fn handle_held_htlc_available( - &self, _message: HeldHtlcAvailable, _responder: Option, + &self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext, + _responder: Option, ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { None } diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index f076e6a9da4..7b0bae82ba8 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -1646,8 +1646,16 @@ where }, #[cfg(async_payments)] ParsedOnionMessageContents::AsyncPayments(AsyncPaymentsMessage::HeldHtlcAvailable(msg)) => { + let context = match context { + Some(MessageContext::AsyncPayments(context)) => context, + Some(_) => { + debug_assert!(false, "Checked in peel_onion_message"); + return + }, + None => return, + }; let response_instructions = self.async_payments_handler.handle_held_htlc_available( - msg, responder + msg, context, responder ); if let Some((msg, instructions)) = response_instructions { let _ = self.handle_onion_message_response(msg, instructions); From fc5f27d85a96f71fa22662e37dfe935fac56f279 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 6 Jan 2025 17:24:50 -0500 Subject: [PATCH 16/23] Fail earlier on expired static invoice Prior to this patch, if we received an expired static invoice we would delay surfacing the payment failure until after the recipient had come online and sent the release_held_htlc OM, which could be a long time later. Now, we'll detect that the invoice is expired as soon as it's received. --- lightning/src/ln/async_payments_tests.rs | 71 ++++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 4 +- lightning/src/ln/outbound_payment.rs | 7 ++- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 01135f625cd..d4a1e4a9aab 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -579,3 +579,74 @@ fn pays_static_invoice() { .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); } + +#[cfg(not(feature = "std"))] +#[test] +fn expired_static_invoice_fail() { + // Test that if we receive an expired static invoice we'll fail the payment. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + const INVOICE_EXPIRY_SECS: u32 = 10; + let relative_expiry = Duration::from_secs(INVOICE_EXPIRY_SECS as u64); + let (offer, static_invoice) = + create_static_invoice(&nodes[1], &nodes[2], Some(relative_expiry), &secp_ctx); + + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) + .unwrap(); + + let invreq_om = nodes[0] + .onion_messenger + .next_onion_message_for_peer(nodes[1].node.get_our_node_id()) + .unwrap(); + let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1; + // TODO: update to not manually send here when we add support for being the recipient's + // always-online counterparty + nodes[1] + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + static_invoice, + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path), + }, + ) + .unwrap(); + let static_invoice_om = nodes[1] + .onion_messenger + .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) + .unwrap(); + + // Wait until the static invoice expires before providing it to the sender. + let block = create_dummy_block( + nodes[0].best_block_hash(), + nodes[0].node.duration_since_epoch().as_secs() as u32 + INVOICE_EXPIRY_SECS + 1, + Vec::new(), + ); + connect_block(&nodes[0], &block); + nodes[0] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om); + + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::PaymentFailed { payment_id: ev_payment_id, reason, .. } => { + assert_eq!(reason.unwrap(), PaymentFailureReason::PaymentExpired); + assert_eq!(ev_payment_id, payment_id); + }, + _ => panic!(), + } + // The sender doesn't reply with InvoiceError right now because the always-online node doesn't + // currently provide them with a reply path to do so. +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b9764a0beaf..3c26b1f5a79 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4744,8 +4744,8 @@ where let best_block_height = self.best_block.read().unwrap().height; let features = self.bolt12_invoice_features(); let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received( - invoice, payment_id, features, best_block_height, &*self.entropy_source, - &self.pending_events + invoice, payment_id, features, best_block_height, self.duration_since_epoch(), + &*self.entropy_source, &self.pending_events ); match outbound_pmts_res { Ok(()) => {}, diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 070a09fde7b..743fff45e62 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1011,7 +1011,7 @@ impl OutboundPayments { #[cfg(async_payments)] pub(super) fn static_invoice_received( &self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures, - best_block_height: u32, entropy_source: ES, + best_block_height: u32, duration_since_epoch: Duration, entropy_source: ES, pending_events: &Mutex)>> ) -> Result<(), Bolt12PaymentError> where ES::Target: EntropySource { macro_rules! abandon_with_entry { @@ -1045,6 +1045,11 @@ impl OutboundPayments { abandon_with_entry!(entry, PaymentFailureReason::UnknownRequiredFeatures); return Err(Bolt12PaymentError::UnknownRequiredFeatures) } + if duration_since_epoch > invoice.created_at().saturating_add(invoice.relative_expiry()) { + abandon_with_entry!(entry, PaymentFailureReason::PaymentExpired); + return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::PaymentExpired)) + } + let amount_msat = match InvoiceBuilder::::amount_msats(invreq) { Ok(amt) => amt, Err(_) => { From 1a5182511876c3224219893bdba8e71af1693a1c Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 6 Jan 2025 14:52:37 -0500 Subject: [PATCH 17/23] Fail out-of-PaymentContext inbound keysends Here we bubble up the payment context into PendingHTLCRouting::ReceiveKeysend and check it when receiving a spontaneous payment prior to generating a claimable event. Prior to this patch, we would have accepted out-of-context keysends sent over blinded paths taken from our BOLT 12 invoices. As a side effect of this, our blinded keysend success test cases now fail, so those tests are now removed. Their coverage is re-added in future commits when we add support for async receive, meaning we're able to receive blinded keysends in the correct payment context. --- lightning/src/ln/async_payments_tests.rs | 169 +---------------------- lightning/src/ln/channelmanager.rs | 17 ++- lightning/src/ln/offers_tests.rs | 70 +++++++++- lightning/src/ln/onion_payment.rs | 1 + 4 files changed, 85 insertions(+), 172 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index d4a1e4a9aab..a2d7755eac7 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -9,11 +9,9 @@ use crate::blinded_path::message::{MessageContext, OffersContext}; use crate::events::{Event, HTLCDestination, MessageSendEventsProvider, PaymentFailureReason}; -use crate::ln::blinded_payment_tests::{blinded_payment_path, get_blinded_route_parameters}; -use crate::ln::channelmanager; +use crate::ln::blinded_payment_tests::get_blinded_route_parameters; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; -use crate::ln::inbound_payment; use crate::ln::msgs::ChannelMessageHandler; use crate::ln::msgs::OnionMessageHandler; use crate::ln::offers_tests; @@ -29,11 +27,8 @@ use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendIns use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::prelude::*; -use crate::routing::router::{PaymentParameters, RouteParameters}; -use crate::sign::NodeSigner; use crate::types::features::Bolt12InvoiceFeatures; use crate::types::payment::{PaymentPreimage, PaymentSecret}; -use crate::util::config::UserConfig; use bitcoin::secp256k1; use bitcoin::secp256k1::Secp256k1; @@ -67,168 +62,6 @@ fn create_static_invoice( (offer, static_invoice) } -#[test] -fn blinded_keysend() { - let chanmon_cfgs = create_chanmon_cfgs(3); - let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); - create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let chan_upd_1_2 = - create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; - - let inbound_payment_key = nodes[2].keys_manager.get_inbound_payment_key(); - let payment_secret = inbound_payment::create_for_spontaneous_payment( - &inbound_payment_key, - None, - u32::MAX, - nodes[2].node.duration_since_epoch().as_secs(), - None, - ) - .unwrap(); - - let amt_msat = 5000; - let keysend_preimage = PaymentPreimage([42; 32]); - let route_params = get_blinded_route_parameters( - amt_msat, - payment_secret, - 1, - 1_0000_0000, - nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), - &[&chan_upd_1_2], - &chanmon_cfgs[2].keys_manager, - ); - - let payment_hash = nodes[0] - .node - .send_spontaneous_payment( - Some(keysend_preimage), - RecipientOnionFields::spontaneous_empty(), - PaymentId(keysend_preimage.0), - route_params, - Retry::Attempts(0), - ) - .unwrap(); - check_added_monitors(&nodes[0], 1); - - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - - let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); - pass_along_path( - &nodes[0], - expected_route[0], - amt_msat, - payment_hash, - Some(payment_secret), - ev.clone(), - true, - Some(keysend_preimage), - ); - claim_payment_along_route(ClaimAlongRouteArgs::new( - &nodes[0], - expected_route, - keysend_preimage, - )); -} - -#[test] -fn blinded_mpp_keysend() { - let chanmon_cfgs = create_chanmon_cfgs(4); - let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]); - let nodes = create_network(4, &node_cfgs, &node_chanmgrs); - - create_announced_chan_between_nodes(&nodes, 0, 1); - create_announced_chan_between_nodes(&nodes, 0, 2); - let chan_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3); - let chan_2_3 = create_announced_chan_between_nodes(&nodes, 2, 3); - - let inbound_payment_key = nodes[3].keys_manager.get_inbound_payment_key(); - let payment_secret = inbound_payment::create_for_spontaneous_payment( - &inbound_payment_key, - None, - u32::MAX, - nodes[3].node.duration_since_epoch().as_secs(), - None, - ) - .unwrap(); - - let amt_msat = 15_000_000; - let keysend_preimage = PaymentPreimage([42; 32]); - let route_params = { - let pay_params = PaymentParameters::blinded(vec![ - blinded_payment_path( - payment_secret, - 1, - 1_0000_0000, - vec![nodes[1].node.get_our_node_id(), nodes[3].node.get_our_node_id()], - &[&chan_1_3.0.contents], - &chanmon_cfgs[3].keys_manager, - ), - blinded_payment_path( - payment_secret, - 1, - 1_0000_0000, - vec![nodes[2].node.get_our_node_id(), nodes[3].node.get_our_node_id()], - &[&chan_2_3.0.contents], - &chanmon_cfgs[3].keys_manager, - ), - ]) - .with_bolt12_features(channelmanager::provided_bolt12_invoice_features( - &UserConfig::default(), - )) - .unwrap(); - RouteParameters::from_payment_params_and_value(pay_params, amt_msat) - }; - - let payment_hash = nodes[0] - .node - .send_spontaneous_payment( - Some(keysend_preimage), - RecipientOnionFields::spontaneous_empty(), - PaymentId(keysend_preimage.0), - route_params, - Retry::Attempts(0), - ) - .unwrap(); - check_added_monitors!(nodes[0], 2); - - let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; - let mut events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 2); - - let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); - pass_along_path( - &nodes[0], - expected_route[0], - amt_msat, - payment_hash.clone(), - Some(payment_secret), - ev.clone(), - false, - Some(keysend_preimage), - ); - - let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); - pass_along_path( - &nodes[0], - expected_route[1], - amt_msat, - payment_hash.clone(), - Some(payment_secret), - ev.clone(), - true, - Some(keysend_preimage), - ); - claim_payment_along_route(ClaimAlongRouteArgs::new( - &nodes[0], - expected_route, - keysend_preimage, - )); -} - #[test] fn invalid_keysend_payment_secret() { let chanmon_cfgs = create_chanmon_cfgs(3); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 3c26b1f5a79..9dc328750c8 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -240,6 +240,11 @@ pub enum PendingHTLCRouting { /// [`PaymentSecret`] and should verify it using our /// [`NodeSigner::get_inbound_payment_key`]. has_recipient_created_payment_secret: bool, + /// The context of the payment included by the recipient in a blinded path, or `None` if a + /// blinded path was not used. + /// + /// Used in part to determine the [`events::PaymentPurpose`]. + payment_context: Option, }, } @@ -6020,7 +6025,7 @@ where PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry, custom_tlvs, requires_blinded_error: _, - has_recipient_created_payment_secret, + has_recipient_created_payment_secret, payment_context, } => { let onion_fields = RecipientOnionFields { payment_secret: payment_data.as_ref().map(|data| data.payment_secret), @@ -6028,7 +6033,8 @@ where custom_tlvs, }; (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), - payment_data, None, None, onion_fields, has_recipient_created_payment_secret) + payment_data, payment_context, None, onion_fields, + has_recipient_created_payment_secret) }, _ => { panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive"); @@ -6225,6 +6231,12 @@ where check_total_value!(purpose); }, OnionPayload::Spontaneous(preimage) => { + if payment_context.is_some() { + if !matches!(payment_context, Some(PaymentContext::AsyncBolt12Offer(_))) { + log_trace!(self.logger, "Failing new HTLC with payment_hash {}: received a keysend payment to a non-async payments context {:#?}", payment_hash, payment_context); + } + fail_htlc!(claimable_htlc, payment_hash); + } let purpose = events::PaymentPurpose::SpontaneousPayment(preimage); check_total_value!(purpose); } @@ -12403,6 +12415,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting, (4, payment_data, option), // Added in 0.0.116 (5, custom_tlvs, optional_vec), (7, has_recipient_created_payment_secret, (default_value, false)), + (9, payment_context, option), }, ); diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 2cbf9e56647..52ded477fc1 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -47,8 +47,8 @@ use crate::blinded_path::IntroductionNode; use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext}; use crate::blinded_path::message::{MessageContext, OffersContext}; -use crate::events::{ClosureReason, Event, MessageSendEventsProvider, PaymentFailureReason, PaymentPurpose}; -use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self}; +use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEventsProvider, PaymentFailureReason, PaymentPurpose}; +use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry, self}; use crate::types::features::Bolt12InvoiceFeatures; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{ChannelMessageHandler, Init, NodeAnnouncement, OnionMessage, OnionMessageHandler, RoutingMessageHandler, SocketAddress, UnsignedGossipMessage, UnsignedNodeAnnouncement}; @@ -62,6 +62,7 @@ use crate::onion_message::messenger::{Destination, PeeledOnion, MessageSendInstr use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::routing::gossip::{NodeAlias, NodeId}; +use crate::routing::router::{PaymentParameters, RouteParameters}; use crate::sign::{NodeSigner, Recipient}; use crate::util::ser::Writeable; @@ -2255,6 +2256,71 @@ fn fails_paying_invoice_with_unknown_required_features() { } } +#[test] +fn rejects_keysend_to_non_static_invoice_path() { + // Test that we'll fail a keysend payment that was sent over a non-static BOLT 12 invoice path. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + + // First pay the offer and save the payment preimage and invoice. + let offer = nodes[1].node.create_offer_builder(None).unwrap().build().unwrap(); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), None).unwrap(); + let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap(); + nodes[1].onion_messenger.handle_onion_message(nodes[0].node.get_our_node_id(), &invreq_om); + let invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap(); + let invoice = extract_invoice(&nodes[0], &invoice_om).0; + nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &invoice_om); + + route_bolt12_payment(&nodes[0], &[&nodes[1]], &invoice); + expect_recent_payment!(nodes[0], RecentPaymentDetails::Pending, payment_id); + + let payment_preimage = match get_event!(nodes[1], Event::PaymentClaimable) { + Event::PaymentClaimable { purpose, .. } => purpose.preimage().unwrap(), + _ => panic!() + }; + + claim_payment(&nodes[0], &[&nodes[1]], payment_preimage); + expect_recent_payment!(&nodes[0], RecentPaymentDetails::Fulfilled, payment_id); + + // Time out the payment from recent payments so we can attempt to pay it again via keysend. + for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS { + nodes[0].node.timer_tick_occurred(); + nodes[1].node.timer_tick_occurred(); + } + + // Pay the invoice via keysend now that we have the preimage and make sure the recipient fails it + // due to incorrect payment context. + let pay_params = PaymentParameters::from_bolt12_invoice(&invoice); + let route_params = RouteParameters::from_payment_params_and_value(pay_params, amt_msat); + let keysend_payment_id = PaymentId([2; 32]); + let payment_hash = nodes[0].node.send_spontaneous_payment( + Some(payment_preimage), RecipientOnionFields::spontaneous_empty(), keysend_payment_id, + route_params, Retry::Attempts(0) + ).unwrap(); + check_added_monitors!(nodes[0], 1); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let route: &[&[&Node]] = &[&[&nodes[1]]]; + + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(payment_preimage) + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, true, PaymentFailedConditions::new()); + nodes[1].logger.assert_log_contains( + "lightning::ln::channelmanager", "received a keysend payment to a non-async payments context", 1 + ); +} + #[test] fn no_double_pay_with_stale_channelmanager() { // This tests the following bug: diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 193cdd1582a..203a528a8fb 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -235,6 +235,7 @@ pub(super) fn create_recv_pending_htlc_info( custom_tlvs, requires_blinded_error, has_recipient_created_payment_secret, + payment_context, } } else if let Some(data) = payment_data { PendingHTLCRouting::Receive { From 1daabbf5d2c8871e177b53ecee5c44abcabe1451 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 7 Jan 2025 20:06:15 -0500 Subject: [PATCH 18/23] Support expiring async payments blinded message paths Store an absolute expiry in blinded message contexts for inbound async payments. Without this, anyone with the path corresponding to this context is able to trivially ask if we're online forever. --- lightning/src/blinded_path/message.rs | 5 +++++ lightning/src/ln/channelmanager.rs | 5 ++++- lightning/src/ln/inbound_payment.rs | 16 ++++++++++------ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 51494e1b21e..1244fec25e5 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -419,6 +419,10 @@ pub enum AsyncPaymentsContext { /// /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable hmac: Hmac, + /// The time as duration since the Unix epoch at which this path expires and messages sent over + /// it should be ignored. Without this, anyone with the path corresponding to this context is + /// able to trivially ask if we're online forever. + path_absolute_expiry: core::time::Duration, }, } @@ -454,6 +458,7 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext, (1, InboundPayment) => { (0, nonce, required), (2, hmac, required), + (4, path_absolute_expiry, required), }, ); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9dc328750c8..387a1e99dfb 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9966,8 +9966,11 @@ where let nonce = Nonce::from_entropy_source(entropy); let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key); + let path_absolute_expiry = Duration::from_secs( + inbound_payment::calculate_absolute_expiry(created_at.as_secs(), relative_expiry_secs) + ); let context = MessageContext::AsyncPayments( - AsyncPaymentsContext::InboundPayment { nonce, hmac } + AsyncPaymentsContext::InboundPayment { nonce, hmac, path_absolute_expiry } ); let async_receive_message_paths = self.create_blinded_paths(context) .map_err(|()| Bolt12SemanticError::MissingPaths)?; diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 72f877978fe..87781793d06 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -213,6 +213,15 @@ pub(super) fn create_for_spontaneous_payment( Ok(construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key)) } +pub(super) fn calculate_absolute_expiry(highest_seen_timestamp: u64, invoice_expiry_delta_secs: u32) -> u64 { + // We assume that highest_seen_timestamp is pretty close to the current time - it's updated when + // we receive a new block with the maximum time we've seen in a header. It should never be more + // than two hours in the future. Thus, we add two hours here as a buffer to ensure we + // absolutely never fail a payment too early. + // Note that we assume that received blocks have reasonably up-to-date timestamps. + highest_seen_timestamp + invoice_expiry_delta_secs as u64 + 7200 +} + fn construct_metadata_bytes(min_value_msat: Option, payment_type: Method, invoice_expiry_delta_secs: u32, highest_seen_timestamp: u64, min_final_cltv_expiry_delta: Option) -> Result<[u8; METADATA_LEN], ()> { if min_value_msat.is_some() && min_value_msat.unwrap() > MAX_VALUE_MSAT { @@ -225,12 +234,7 @@ fn construct_metadata_bytes(min_value_msat: Option, payment_type: Method, }; min_amt_msat_bytes[0] |= (payment_type as u8) << METHOD_TYPE_OFFSET; - // We assume that highest_seen_timestamp is pretty close to the current time - it's updated when - // we receive a new block with the maximum time we've seen in a header. It should never be more - // than two hours in the future. Thus, we add two hours here as a buffer to ensure we - // absolutely never fail a payment too early. - // Note that we assume that received blocks have reasonably up-to-date timestamps. - let expiry_timestamp = highest_seen_timestamp + invoice_expiry_delta_secs as u64 + 7200; + let expiry_timestamp = calculate_absolute_expiry(highest_seen_timestamp, invoice_expiry_delta_secs); let mut expiry_bytes = expiry_timestamp.to_be_bytes(); // `min_value_msat` should fit in (64 bits - 3 payment type bits =) 61 bits as an unsigned integer. From 5ebdd176b3e916c515ff934a4bd0e412e47cb84c Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 7 Jan 2025 20:08:50 -0500 Subject: [PATCH 19/23] Release async payment HTLCs held upstream via OM If we receive a message that an HTLC is being held upstream for us, send a reply onion message back releasing it since we are online to receive the corresponding payment. --- lightning/src/ln/channelmanager.rs | 15 ++++++++++++++- lightning/src/offers/signer.rs | 7 +++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 387a1e99dfb..b2d6c764944 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12210,7 +12210,20 @@ where &self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext, _responder: Option ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { - None + #[cfg(async_payments)] { + match _context { + AsyncPaymentsContext::InboundPayment { nonce, hmac, path_absolute_expiry } => { + if let Err(()) = signer::verify_held_htlc_available_context( + nonce, hmac, &self.inbound_payment_key + ) { return None } + if self.duration_since_epoch() > path_absolute_expiry { return None } + }, + _ => return None + } + return _responder.map(|responder| (ReleaseHeldHtlc {}, responder.respond())) + } + #[cfg(not(async_payments))] + return None } fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 7deff734b34..93ff4ecea3b 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -501,3 +501,10 @@ pub(crate) fn hmac_for_held_htlc_available_context( Hmac::from_engine(hmac) } + +#[cfg(async_payments)] +pub(crate) fn verify_held_htlc_available_context( + nonce: Nonce, hmac: Hmac, expanded_key: &ExpandedKey, +) -> Result<(), ()> { + if hmac_for_held_htlc_available_context(nonce, expanded_key) == hmac { Ok(()) } else { Err(()) } +} From e0d18a1f23014f60bb161f22c5f5032fb9c4a923 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 30 Jul 2024 16:53:52 -0700 Subject: [PATCH 20/23] Bubble up invreq from htlc onion to forwarding flow As part of receiving an async payment, we need to verify the sender's original invoice request. Therefore, add support for parsing the invreq contained in the onion and storing it in PendingHTLCForwards to prepare for when we add this verification in an upcoming commit. The invreq also needs to be bubbled up for inclusion in the PaymentClaimable event's PaymentPurpose. --- lightning/src/ln/channelmanager.rs | 11 +++++++---- lightning/src/ln/msgs.rs | 11 ++++++++--- lightning/src/ln/onion_payment.rs | 10 ++++++---- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b2d6c764944..ede19cd2815 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -240,6 +240,8 @@ pub enum PendingHTLCRouting { /// [`PaymentSecret`] and should verify it using our /// [`NodeSigner::get_inbound_payment_key`]. has_recipient_created_payment_secret: bool, + /// The [`InvoiceRequest`] associated with the [`Offer`] corresponding to this payment. + invoice_request: Option, /// The context of the payment included by the recipient in a blinded path, or `None` if a /// blinded path was not used. /// @@ -6008,7 +6010,7 @@ where let blinded_failure = routing.blinded_failure(); let ( cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret, - mut onion_fields, has_recipient_created_payment_secret + mut onion_fields, has_recipient_created_payment_secret, _invoice_request_opt ) = match routing { PendingHTLCRouting::Receive { payment_data, payment_metadata, payment_context, @@ -6020,12 +6022,12 @@ where payment_metadata, custom_tlvs }; (incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data }, Some(payment_data), payment_context, phantom_shared_secret, onion_fields, - true) + true, None) }, PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, incoming_cltv_expiry, custom_tlvs, requires_blinded_error: _, - has_recipient_created_payment_secret, payment_context, + has_recipient_created_payment_secret, payment_context, invoice_request, } => { let onion_fields = RecipientOnionFields { payment_secret: payment_data.as_ref().map(|data| data.payment_secret), @@ -6034,7 +6036,7 @@ where }; (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), payment_data, payment_context, None, onion_fields, - has_recipient_created_payment_secret) + has_recipient_created_payment_secret, invoice_request) }, _ => { panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive"); @@ -12432,6 +12434,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting, (5, custom_tlvs, optional_vec), (7, has_recipient_created_payment_secret, (default_value, false)), (9, payment_context, option), + (11, invoice_request, option), }, ); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 659ec65f6cf..ae89c2d6d50 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -37,6 +37,7 @@ use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::types::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; use crate::ln::onion_utils; +use crate::offers::invoice_request::InvoiceRequest; use crate::onion_message; use crate::sign::{NodeSigner, Recipient}; @@ -1791,6 +1792,7 @@ mod fuzzy_internal_msgs { payment_context: PaymentContext, intro_node_blinding_point: Option, keysend_preimage: Option, + invoice_request: Option, custom_tlvs: Vec<(u64, Vec)>, } } @@ -2852,6 +2854,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh let mut payment_metadata: Option>> = None; let mut total_msat = None; let mut keysend_preimage: Option = None; + let mut invoice_request: Option = None; let mut custom_tlvs = Vec::new(); let tlv_len = BigSize::read(r)?; @@ -2865,6 +2868,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh (12, intro_node_blinding_point, option), (16, payment_metadata, option), (18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (77_777, invoice_request, option), // See https://github.com/lightning/blips/blob/master/blip-0003.md (5482373484, keysend_preimage, option) }, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result { @@ -2895,7 +2899,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh short_channel_id, payment_relay, payment_constraints, features, next_blinding_override })} => { if amt.is_some() || cltv_value.is_some() || total_msat.is_some() || - keysend_preimage.is_some() + keysend_preimage.is_some() || invoice_request.is_some() { return Err(DecodeError::InvalidValue) } @@ -2928,13 +2932,14 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh payment_context, intro_node_blinding_point, keysend_preimage, + invoice_request, custom_tlvs, }) }, } } else if let Some(short_channel_id) = short_id { if payment_data.is_some() || payment_metadata.is_some() || encrypted_tlvs_opt.is_some() || - total_msat.is_some() + total_msat.is_some() || invoice_request.is_some() { return Err(DecodeError::InvalidValue) } Ok(Self::Forward { short_channel_id, @@ -2942,7 +2947,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, }) } else { - if encrypted_tlvs_opt.is_some() || total_msat.is_some() { + if encrypted_tlvs_opt.is_some() || total_msat.is_some() || invoice_request.is_some() { return Err(DecodeError::InvalidValue) } if let Some(data) = &payment_data { diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 203a528a8fb..96308f0a647 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -135,18 +135,19 @@ pub(super) fn create_recv_pending_htlc_info( ) -> Result { let ( payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry, - payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret + payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret, + invoice_request ) = match hop_data { msgs::InboundOnionPayload::Receive { payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, payment_metadata, .. } => (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, - cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none()), + cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None), msgs::InboundOnionPayload::BlindedReceive { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage, - custom_tlvs + custom_tlvs, invoice_request } => { check_blinded_payment_constraints( sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints @@ -161,7 +162,7 @@ pub(super) fn create_recv_pending_htlc_info( let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat }; (Some(payment_data), keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context), - intro_node_blinding_point.is_none(), true) + intro_node_blinding_point.is_none(), true, invoice_request) } msgs::InboundOnionPayload::Forward { .. } => { return Err(InboundHTLCErr { @@ -236,6 +237,7 @@ pub(super) fn create_recv_pending_htlc_info( requires_blinded_error, has_recipient_created_payment_secret, payment_context, + invoice_request, } } else if let Some(data) = payment_data { PendingHTLCRouting::Receive { From d93aecc8978dbd04f246cd5c4898755ea8097844 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 2 Aug 2024 17:19:54 -0700 Subject: [PATCH 21/23] Support receiving async payment HTLCs After a lot of setup in prior commits, here we finally finish support for receiving HTLCs paid to static BOLT 12 invoices. It amounts to verifying the invoice request contained within the onion and generating the right PaymentPurpose for the claimable event. --- lightning/src/events/mod.rs | 26 +++++++++----- lightning/src/ln/channelmanager.rs | 54 +++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index b3e05cfe4d8..e4ff15a1a1b 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -28,6 +28,7 @@ use crate::ln::msgs; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice_request::VerifiedInvoiceRequest; use crate::onion_message::messenger::Responder; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters}; @@ -180,7 +181,7 @@ impl PaymentPurpose { pub(crate) fn from_parts( payment_preimage: Option, payment_secret: PaymentSecret, - payment_context: Option, + payment_context: Option, invreq: Option, ) -> Result { match payment_context { None => { @@ -203,11 +204,18 @@ impl PaymentPurpose { payment_context: context, }) }, - Some(PaymentContext::AsyncBolt12Offer(_context)) => { - // This code will change to return Self::Bolt12OfferPayment when we add support for async - // receive. - Err(()) - }, + Some(PaymentContext::AsyncBolt12Offer(_)) => { + let invoice_request = invreq.ok_or(())?; + if payment_preimage.is_none() { return Err(()) } + Ok(PaymentPurpose::Bolt12OfferPayment { + payment_preimage, + payment_secret, + payment_context: Bolt12OfferContext { + offer_id: invoice_request.offer_id, + invoice_request: invoice_request.fields(), + }, + }) + } } } } @@ -1190,12 +1198,12 @@ pub enum Event { /// events generated or serialized by versions prior to 0.0.122. next_user_channel_id: Option, /// The node id of the previous node. - /// + /// /// This is only `None` for HTLCs received prior to 0.1 or for events serialized by /// versions prior to 0.1 prev_node_id: Option, /// The node id of the next node. - /// + /// /// This is only `None` for HTLCs received prior to 0.1 or for events serialized by /// versions prior to 0.1 next_node_id: Option, @@ -1872,7 +1880,7 @@ impl MaybeReadable for Event { (13, payment_id, option), }); let purpose = match payment_secret { - Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context) + Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context, None) .map_err(|()| msgs::DecodeError::InvalidValue)?, None if payment_preimage.is_some() => PaymentPurpose::SpontaneousPayment(payment_preimage.unwrap()), None => return Err(msgs::DecodeError::InvalidValue), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ede19cd2815..ca5d07cf3cc 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -36,7 +36,7 @@ use crate::events::FundingInfo; use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; use crate::blinded_path::NodeIdLookUp; use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode}; -use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; +use crate::blinded_path::payment::{AsyncBolt12OfferContext, BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; use crate::chain; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; @@ -89,7 +89,6 @@ use crate::util::ser::TransactionU16LenLimited; use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; #[cfg(async_payments)] use { - crate::blinded_path::payment::AsyncBolt12OfferContext, crate::offers::offer::Amount, crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder}, }; @@ -6010,7 +6009,7 @@ where let blinded_failure = routing.blinded_failure(); let ( cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret, - mut onion_fields, has_recipient_created_payment_secret, _invoice_request_opt + mut onion_fields, has_recipient_created_payment_secret, invoice_request_opt ) = match routing { PendingHTLCRouting::Receive { payment_data, payment_metadata, payment_context, @@ -6224,6 +6223,7 @@ where payment_preimage, payment_data.payment_secret, payment_context, + None, ) { Ok(purpose) => purpose, Err(()) => { @@ -6232,14 +6232,50 @@ where }; check_total_value!(purpose); }, - OnionPayload::Spontaneous(preimage) => { - if payment_context.is_some() { - if !matches!(payment_context, Some(PaymentContext::AsyncBolt12Offer(_))) { - log_trace!(self.logger, "Failing new HTLC with payment_hash {}: received a keysend payment to a non-async payments context {:#?}", payment_hash, payment_context); + OnionPayload::Spontaneous(keysend_preimage) => { + let purpose = if let Some(PaymentContext::AsyncBolt12Offer( + AsyncBolt12OfferContext { offer_nonce } + )) = payment_context { + let payment_data = match payment_data { + Some(data) => data, + None => { + debug_assert!(false, "We checked that payment_data is Some above"); + fail_htlc!(claimable_htlc, payment_hash); + }, + }; + + let verified_invreq = match invoice_request_opt + .and_then(|invreq| invreq.verify_using_recipient_data( + offer_nonce, &self.inbound_payment_key, &self.secp_ctx + ).ok()) + { + Some(verified_invreq) => { + if let Some(invreq_amt_msat) = verified_invreq.amount_msats() { + if payment_data.total_msat < invreq_amt_msat { + fail_htlc!(claimable_htlc, payment_hash); + } + } + verified_invreq + }, + None => { + fail_htlc!(claimable_htlc, payment_hash); + } + }; + match events::PaymentPurpose::from_parts( + Some(keysend_preimage), payment_data.payment_secret, payment_context, + Some(verified_invreq), + ) { + Ok(purpose) => purpose, + Err(()) => { + fail_htlc!(claimable_htlc, payment_hash); + } } + } else if payment_context.is_some() { + log_trace!(self.logger, "Failing new HTLC with payment_hash {}: received a keysend payment to a non-async payments context {:#?}", payment_hash, payment_context); fail_htlc!(claimable_htlc, payment_hash); - } - let purpose = events::PaymentPurpose::SpontaneousPayment(preimage); + } else { + events::PaymentPurpose::SpontaneousPayment(keysend_preimage) + }; check_total_value!(purpose); } } From d19ebae7355ef4f1161e3041be168c5515ad99dd Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 7 Jan 2025 21:22:05 -0500 Subject: [PATCH 22/23] Test utils: fix config overrides for private channels Previously, this test util did not account for config overrides supplied at node creation time. Uncovered because it caused test nodes to be unable to forward over private channels created with this util because that is not the default. --- lightning/src/ln/functional_test_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 5a5f0c78029..3d67a57c837 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1507,7 +1507,7 @@ pub fn create_announced_chan_between_nodes_with_value<'a, 'b, 'c: 'd, 'd>(nodes: } pub fn create_unannounced_chan_between_nodes_with_value<'a, 'b, 'c, 'd>(nodes: &'a Vec>, a: usize, b: usize, channel_value: u64, push_msat: u64) -> (msgs::ChannelReady, Transaction) { - let mut no_announce_cfg = test_default_channel_config(); + let mut no_announce_cfg = nodes[a].node.get_current_default_configuration().clone(); no_announce_cfg.channel_handshake_config.announce_for_forwarding = false; nodes[a].node.create_channel(nodes[b].node.get_our_node_id(), channel_value, push_msat, 42, None, Some(no_announce_cfg)).unwrap(); let open_channel = get_event_msg!(nodes[a], MessageSendEvent::SendOpenChannel, nodes[b].node.get_our_node_id()); From 6f7fcba5e0e3afda28ff9793db66608c4115406f Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 8 Jan 2025 22:13:03 -0500 Subject: [PATCH 23/23] Test async receive In the previous commit we completed support for async receive from an always-online sender to an often-offline receiver, minus support for acting as the async receiver's always-online channel counterparty. --- lightning/src/blinded_path/payment.rs | 26 +- lightning/src/ln/async_payments_tests.rs | 739 ++++++++++++++++++++-- lightning/src/ln/blinded_payment_tests.rs | 12 +- lightning/src/ln/channelmanager.rs | 13 + lightning/src/ln/functional_test_utils.rs | 27 +- 5 files changed, 733 insertions(+), 84 deletions(-) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index cf5af7d784e..42913628872 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -12,6 +12,7 @@ use bitcoin::hashes::hmac::Hmac; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; +use bitcoin::secp256k1::ecdh::SharedSecret; use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp}; use crate::blinded_path::utils; @@ -170,15 +171,8 @@ impl BlindedPaymentPath { NL::Target: NodeIdLookUp, T: secp256k1::Signing + secp256k1::Verification, { - let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?; - let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); - let encrypted_control_tlvs = &self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload; - let mut s = Cursor::new(encrypted_control_tlvs); - let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); - match ChaChaPolyReadAdapter::read(&mut reader, rho) { - Ok(ChaChaPolyReadAdapter { - readable: BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }) - }) => { + match self.decrypt_intro_payload::(node_signer) { + Ok((BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }), control_tlvs_ss)) => { let next_node_id = match node_id_lookup.next_node_id(short_channel_id) { Some(node_id) => node_id, None => return Err(()), @@ -195,6 +189,20 @@ impl BlindedPaymentPath { } } + pub(crate) fn decrypt_intro_payload( + &self, node_signer: &NS + ) -> Result<(BlindedPaymentTlvs, SharedSecret), ()> where NS::Target: NodeSigner { + let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?; + let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); + let encrypted_control_tlvs = &self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload; + let mut s = Cursor::new(encrypted_control_tlvs); + let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); + match ChaChaPolyReadAdapter::read(&mut reader, rho) { + Ok(ChaChaPolyReadAdapter { readable, .. }) => Ok((readable, control_tlvs_ss)), + _ => Err(()) + } + } + pub(crate) fn inner_blinded_path(&self) -> &BlindedPath { &self.inner_path } diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index a2d7755eac7..2d1ae0f1efc 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -8,33 +8,100 @@ // licenses. use crate::blinded_path::message::{MessageContext, OffersContext}; -use crate::events::{Event, HTLCDestination, MessageSendEventsProvider, PaymentFailureReason}; -use crate::ln::blinded_payment_tests::get_blinded_route_parameters; +use crate::blinded_path::payment::PaymentContext; +use crate::blinded_path::payment::{AsyncBolt12OfferContext, BlindedPaymentTlvs}; +use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS}; +use crate::events::{ + Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason, +}; +use crate::ln::blinded_payment_tests::{fail_blinded_htlc_backwards, get_blinded_route_parameters}; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; +use crate::ln::msgs; use crate::ln::msgs::ChannelMessageHandler; use crate::ln::msgs::OnionMessageHandler; use crate::ln::offers_tests; use crate::ln::onion_utils::INVALID_ONION_BLINDING; +use crate::ln::outbound_payment::PendingOutboundPayment; use crate::ln::outbound_payment::Retry; +use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::offer::Offer; use crate::offers::static_invoice::StaticInvoice; -use crate::onion_message::async_payments::{ - AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc, -}; +use crate::onion_message::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler}; use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions}; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::prelude::*; +use crate::routing::router::{Payee, PaymentParameters}; +use crate::sign::NodeSigner; +use crate::sync::Mutex; use crate::types::features::Bolt12InvoiceFeatures; -use crate::types::payment::{PaymentPreimage, PaymentSecret}; +use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; +use bitcoin::constants::ChainHash; +use bitcoin::network::Network; use bitcoin::secp256k1; use bitcoin::secp256k1::Secp256k1; use core::convert::Infallible; use core::time::Duration; +// Goes through the async receive onion message flow, returning the final release_held_htlc OM. +// +// Assumes the held_htlc_available message will be sent: +// sender -> always_online_recipient_counterparty -> recipient. +// +// Returns: (held_htlc_available_om, release_held_htlc_om) +fn pass_async_payments_oms( + static_invoice: StaticInvoice, sender: &Node, always_online_recipient_counterparty: &Node, + recipient: &Node, +) -> (msgs::OnionMessage, msgs::OnionMessage) { + let sender_node_id = sender.node.get_our_node_id(); + let always_online_node_id = always_online_recipient_counterparty.node.get_our_node_id(); + + // Don't forward the invreq since we don't support retrieving the static invoice from the + // recipient's LSP yet, instead manually construct the response. + let invreq_om = + sender.onion_messenger.next_onion_message_for_peer(always_online_node_id).unwrap(); + let invreq_reply_path = + offers_tests::extract_invoice_request(always_online_recipient_counterparty, &invreq_om).1; + + always_online_recipient_counterparty + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + static_invoice, + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path), + }, + ) + .unwrap(); + let static_invoice_om = always_online_recipient_counterparty + .onion_messenger + .next_onion_message_for_peer(sender_node_id) + .unwrap(); + sender.onion_messenger.handle_onion_message(always_online_node_id, &static_invoice_om); + + let held_htlc_available_om_0_1 = + sender.onion_messenger.next_onion_message_for_peer(always_online_node_id).unwrap(); + always_online_recipient_counterparty + .onion_messenger + .handle_onion_message(sender_node_id, &held_htlc_available_om_0_1); + let held_htlc_available_om_1_2 = always_online_recipient_counterparty + .onion_messenger + .next_onion_message_for_peer(recipient.node.get_our_node_id()) + .unwrap(); + recipient + .onion_messenger + .handle_onion_message(always_online_node_id, &held_htlc_available_om_1_2); + + ( + held_htlc_available_om_1_2, + recipient.onion_messenger.next_onion_message_for_peer(sender_node_id).unwrap(), + ) +} + fn create_static_invoice( always_online_counterparty: &Node, recipient: &Node, relative_expiry: Option, secp_ctx: &Secp256k1, @@ -319,17 +386,25 @@ fn ignore_unexpected_static_invoice() { } #[test] -fn pays_static_invoice() { - // Test that we support the async payments flow up to and including sending the actual payment. - // Async receive is not yet supported so we don't complete the payment yet. +fn async_receive_flow_success() { + // Test that an always-online sender can successfully pay an async receiver. let secp_ctx = Secp256k1::new(); let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = + create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + let relative_expiry = Duration::from_secs(1000); let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[2], Some(relative_expiry), &secp_ctx); @@ -342,68 +417,16 @@ fn pays_static_invoice() { .node .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) .unwrap(); - - // Don't forward the invreq since we don't support retrieving the static invoice from the - // recipient's LSP yet, instead manually construct the response. - let invreq_om = nodes[0] - .onion_messenger - .next_onion_message_for_peer(nodes[1].node.get_our_node_id()) - .unwrap(); - let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1; - - nodes[1] - .onion_messenger - .send_onion_message( - ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( - static_invoice, - )), - MessageSendInstructions::WithoutReplyPath { - destination: Destination::BlindedPath(invreq_reply_path), - }, - ) - .unwrap(); - let static_invoice_om = nodes[1] - .onion_messenger - .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) - .unwrap(); - nodes[0] - .onion_messenger - .handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om); - let mut async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node); - assert!(!async_pmts_msgs.is_empty()); - assert!(async_pmts_msgs - .iter() - .all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_)))); - - // Manually send the message and context releasing the HTLC since the recipient doesn't support - // responding themselves yet. - let held_htlc_avail_reply_path = match async_pmts_msgs.pop().unwrap().1 { - MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => reply_path, - _ => panic!(), - }; - nodes[2] - .onion_messenger - .send_onion_message( - ParsedOnionMessageContents::::AsyncPayments( - AsyncPaymentsMessage::ReleaseHeldHtlc(ReleaseHeldHtlc {}), - ), - MessageSendInstructions::WithoutReplyPath { - destination: Destination::BlindedPath(held_htlc_avail_reply_path), - }, - ) - .unwrap(); - - let release_held_htlc_om = nodes[2] - .onion_messenger - .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) - .unwrap(); + let release_held_htlc_om = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]).1; nodes[0] .onion_messenger .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); // Check that we've queued the HTLCs of the async keysend payment. - let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); - assert_eq!(htlc_updates.update_add_htlcs.len(), 1); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); check_added_monitors!(nodes[0], 1); // Receiving a duplicate release_htlc message doesn't result in duplicate payment. @@ -411,6 +434,12 @@ fn pays_static_invoice() { .onion_messenger .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); } #[cfg(not(feature = "std"))] @@ -483,3 +512,579 @@ fn expired_static_invoice_fail() { // The sender doesn't reply with InvoiceError right now because the always-online node doesn't // currently provide them with a reply path to do so. } + +#[test] +fn async_receive_mpp() { + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(4); + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = create_node_chanmgrs( + 4, + &node_cfgs, + &[None, Some(allow_priv_chan_fwds_cfg.clone()), Some(allow_priv_chan_fwds_cfg), None], + ); + let nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + // Create this network topology: + // n1 + // / \ + // n0 n3 + // \ / + // n2 + create_announced_chan_between_nodes(&nodes, 0, 1); + create_announced_chan_between_nodes(&nodes, 0, 2); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 3, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[3], None, &secp_ctx); + + // In other tests we hardcode the sender's random bytes so we can predict the keysend preimage to + // check later in the test, but that doesn't work for MPP because it causes the session_privs for + // the different MPP parts to not be unique. + let amt_msat = 15_000_000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), None) + .unwrap(); + let release_held_htlc_om_3_0 = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[3]).1; + nodes[0] + .onion_messenger + .handle_onion_message(nodes[3].node.get_our_node_id(), &release_held_htlc_om_3_0); + check_added_monitors(&nodes[0], 2); + + let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 2); + + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let payment_hash = match ev { + MessageSendEvent::UpdateHTLCs { ref updates, .. } => { + updates.update_add_htlcs[0].payment_hash + }, + _ => panic!(), + }; + + let args = PassAlongPathArgs::new(&nodes[0], expected_route[0], amt_msat, payment_hash, ev) + .without_claimable_event(); + do_pass_along_path(args); + + let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); + let args = PassAlongPathArgs::new(&nodes[0], expected_route[1], amt_msat, payment_hash, ev); + let claimable_ev = do_pass_along_path(args).unwrap(); + let keysend_preimage = match claimable_ev { + crate::events::Event::PaymentClaimable { + purpose: crate::events::PaymentPurpose::Bolt12OfferPayment { payment_preimage, .. }, + .. + } => payment_preimage.unwrap(), + _ => panic!(), + }; + claim_payment_along_route(ClaimAlongRouteArgs::new( + &nodes[0], + expected_route, + keysend_preimage, + )); +} + +#[test] +fn amount_doesnt_match_invreq() { + // Ensure that we'll fail an async payment backwards if the amount in the HTLC is lower than the + // amount from the original invoice request. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(4); + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + // Make one blinded path's fees slightly higher so they are tried in a deterministic order. + let mut higher_fee_chan_cfg = allow_priv_chan_fwds_cfg.clone(); + higher_fee_chan_cfg.channel_config.forwarding_fee_base_msat += 5000; + let node_chanmgrs = create_node_chanmgrs( + 4, + &node_cfgs, + &[None, Some(allow_priv_chan_fwds_cfg), Some(higher_fee_chan_cfg), None], + ); + let nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + // Create this network topology so nodes[0] has a blinded route hint to retry over. + // n1 + // / \ + // n0 n3 + // \ / + // n2 + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 3, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + + let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[3], None, &secp_ctx); + + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), None) + .unwrap(); + let release_held_htlc_om_3_0 = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[3]).1; + + // Replace the invoice request contained within outbound_payments before sending so the invreq + // amount doesn't match the onion amount when the HTLC gets to the recipient. + let mut outbounds = + nodes[0].node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + let valid_invreq; + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::StaticInvoiceReceived { invoice_request, .. }) => { + valid_invreq = invoice_request.clone(); + *invoice_request = offer + .request_invoice( + &nodes[0].keys_manager.get_inbound_payment_key(), + Nonce::from_entropy_source(nodes[0].keys_manager), + &secp_ctx, + payment_id, + ) + .unwrap() + .amount_msats(amt_msat + 1) + .unwrap() + .chain_hash(ChainHash::using_genesis_block(Network::Testnet)) + .unwrap() + .build_and_sign() + .unwrap(); + }, + _ => panic!(), + } + core::mem::drop(outbounds); + + nodes[0] + .onion_messenger + .handle_onion_message(nodes[3].node.get_our_node_id(), &release_held_htlc_om_3_0); + check_added_monitors(&nodes[0], 1); + + // Check that we've queued the HTLCs of the async keysend payment. + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + + // Modify the invoice request stored in our outbounds to be the correct one, to make sure the + // payment retry will succeed after we finish failing the invalid HTLC back. + let mut outbounds = + nodes[0].node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { invoice_request, .. }) => { + *invoice_request = Some(valid_invreq); + }, + _ => panic!(), + } + core::mem::drop(outbounds); + + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[3]], true); + + // The retry with the correct invoice request should succeed. + nodes[0].node.process_pending_htlc_forwards(); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + check_added_monitors!(nodes[0], 1); + let route: &[&[&Node]] = &[&[&nodes[2], &nodes[3]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); +} + +#[test] +fn reject_missing_invreq() { + // Ensure we'll fail an async payment backwards if the HTLC onion doesn't contain the sender's + // original invoice request. + let mut valid_invreq: Mutex> = Mutex::new(None); + + invalid_async_receive_with_retry( + |sender, _, payment_id| { + // Remove the invoice request from our Retryable payment so we don't include it in the onion on + // retry. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + let valid_invreq_from_outbounds = match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { invoice_request, .. }) => { + assert!(invoice_request.is_some()); + invoice_request.take() + }, + _ => panic!(), + }; + *valid_invreq.lock().unwrap() = valid_invreq_from_outbounds; + }, + |sender, payment_id| { + // Re-add the invoice request so we include it in the onion on the next retry. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { invoice_request, .. }) => { + *invoice_request = valid_invreq.lock().unwrap().take(); + }, + _ => panic!(), + } + }, + ); +} + +#[test] +fn reject_bad_payment_secret() { + // Ensure we'll fail an async payment backwards if the payment secret in the onion is invalid. + + let mut valid_payment_params: Mutex> = Mutex::new(None); + invalid_async_receive_with_retry( + |sender, recipient, payment_id| { + // Store invalid payment paths in the sender's outbound Retryable payment to induce the failure + // on the recipient's end. Store multiple paths so the sender still thinks they can retry after + // the failure we're about to cause below. + let mut invalid_blinded_payment_paths = Vec::new(); + for i in 0..2 { + let mut paths = recipient + .node + .test_create_blinded_payment_paths( + None, + PaymentSecret([42; 32]), // invalid payment secret + PaymentContext::AsyncBolt12Offer(AsyncBolt12OfferContext { + // We don't reach the point of checking the invreq nonce due to the invalid payment secret + offer_nonce: Nonce([i; Nonce::LENGTH]), + }), + u32::MAX, + ) + .unwrap(); + invalid_blinded_payment_paths.append(&mut paths); + } + + // Modify the outbound payment parameters to use payment paths with an invalid payment secret. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + let valid_params_from_outbounds = match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { ref mut payment_params, .. }) => { + assert!(payment_params.is_some()); + let valid_params = payment_params.clone(); + if let Payee::Blinded { ref mut route_hints, .. } = + &mut payment_params.as_mut().unwrap().payee + { + core::mem::swap(route_hints, &mut invalid_blinded_payment_paths); + } else { + panic!() + } + valid_params + }, + _ => panic!(), + }; + *valid_payment_params.lock().unwrap() = valid_params_from_outbounds; + }, + |sender, payment_id| { + // Re-add the valid payment params so we use the right payment secret on the next retry. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { payment_params, .. }) => { + *payment_params = valid_payment_params.lock().unwrap().take(); + }, + _ => panic!(), + } + }, + ); +} + +fn invalid_async_receive_with_retry( + mut modify_outbounds_for_failure: F1, mut modify_outbounds_for_success: F2, +) where + F1: FnMut(&Node, &Node, PaymentId), + F2: FnMut(&Node, PaymentId), +{ + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = + create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + let blinded_paths_to_always_online_node = nodes[1] + .message_router + .create_blinded_paths( + nodes[1].node.get_our_node_id(), + MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), + Vec::new(), + &secp_ctx, + ) + .unwrap(); + let (offer_builder, offer_nonce) = nodes[2] + .node + .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .unwrap(); + let offer = offer_builder.build().unwrap(); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + + // Hardcode the payment paths so nodes[0] has something to retry over. Set all of these paths to + // use the same nodes to avoid complicating the test with a bunch of extra nodes. + let mut static_invoice_paths = Vec::new(); + for _ in 0..3 { + let static_inv_for_path = nodes[2] + .node + .create_static_invoice_builder(&offer, offer_nonce, None) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + static_invoice_paths.push(static_inv_for_path.payment_paths()[0].clone()); + } + nodes[2].router.expect_blinded_payment_paths(static_invoice_paths); + + let static_invoice = nodes[2] + .node + .create_static_invoice_builder(&offer, offer_nonce, None) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(2), None) + .unwrap(); + let release_held_htlc_om_2_0 = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]).1; + nodes[0] + .onion_messenger + .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om_2_0); + check_added_monitors(&nodes[0], 1); + + // Check that we've queued the HTLCs of the async keysend payment. + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + + // Fail the HTLC backwards to enable us to more easily modify the now-Retryable outbound to test + // failures on the recipient's end. + nodes[2].node.fail_htlc_backwards(&payment_hash); + expect_pending_htlcs_forwardable_conditions( + nodes[2].node.get_and_clear_pending_events(), + &[HTLCDestination::FailedPayment { payment_hash }], + ); + nodes[2].node.process_pending_htlc_forwards(); + check_added_monitors!(nodes[2], 1); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2]], true); + + // Trigger a retry and make sure it fails after calling the closure that induces recipient + // failure. + modify_outbounds_for_failure(&nodes[0], &nodes[2], payment_id); + nodes[0].node.process_pending_htlc_forwards(); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + check_added_monitors!(nodes[0], 1); + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2]], true); + + // The retry after calling the 2nd closure should succeed. + modify_outbounds_for_success(&nodes[0], payment_id); + nodes[0].node.process_pending_htlc_forwards(); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + check_added_monitors!(nodes[0], 1); + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); +} + +#[cfg(not(feature = "std"))] +#[test] +fn expired_static_invoice_message_path() { + // Test that if we receive a held_htlc_available message over an expired blinded path, we'll + // ignore it. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + const INVOICE_EXPIRY_SECS: u32 = 10; + let (offer, static_invoice) = create_static_invoice( + &nodes[1], + &nodes[2], + Some(Duration::from_secs(INVOICE_EXPIRY_SECS as u64)), + &secp_ctx, + ); + + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), None) + .unwrap(); + + // While the invoice is unexpired, respond with release_held_htlc. + let (held_htlc_available_om, _release_held_htlc_om) = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]); + + // After the invoice is expired, ignore inbound held_htlc_available messages over the path. + let path_absolute_expiry = crate::ln::inbound_payment::calculate_absolute_expiry( + nodes[2].node.duration_since_epoch().as_secs(), + INVOICE_EXPIRY_SECS, + ); + let block = create_dummy_block( + nodes[2].best_block_hash(), + (path_absolute_expiry + 1) as u32, + Vec::new(), + ); + connect_block(&nodes[2], &block); + nodes[2] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &held_htlc_available_om); + for i in 0..2 { + assert!(nodes[2] + .onion_messenger + .next_onion_message_for_peer(nodes[i].node.get_our_node_id()) + .is_none()); + } +} + +#[test] +fn expired_static_invoice_payment_path() { + // Test that we'll reject inbound payments to expired payment paths. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = + create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + // Make sure all nodes are at the same block height in preparation for CLTV timeout things. + let node_max_height = + nodes.iter().map(|node| node.blocks.lock().unwrap().len()).max().unwrap() as u32; + connect_blocks(&nodes[0], node_max_height - nodes[0].best_block_info().1); + connect_blocks(&nodes[1], node_max_height - nodes[1].best_block_info().1); + connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1); + + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + + // Hardcode the blinded payment path returned by the router so we can expire it via mining blocks. + let (_, static_invoice_expired_paths) = + create_static_invoice(&nodes[1], &nodes[2], None, &secp_ctx); + nodes[2] + .router + .expect_blinded_payment_paths(static_invoice_expired_paths.payment_paths().to_vec()); + + // Extract the expiry height from the to-be-expired blinded payment path. + let final_max_cltv_expiry = { + let mut blinded_path = static_invoice_expired_paths.payment_paths().to_vec().pop().unwrap(); + blinded_path + .advance_path_by_one(&nodes[1].keys_manager, &nodes[1].node, &secp_ctx) + .unwrap(); + match blinded_path.decrypt_intro_payload(&nodes[2].keys_manager).unwrap().0 { + BlindedPaymentTlvs::Receive(tlvs) => tlvs.tlvs.payment_constraints.max_cltv_expiry, + _ => panic!(), + } + }; + + // Mine a bunch of blocks so the hardcoded path's `max_cltv_expiry` is expired at the recipient's + // end by the time the payment arrives. + let min_cltv_expiry_delta = test_default_channel_config().channel_config.cltv_expiry_delta; + connect_blocks( + &nodes[0], + final_max_cltv_expiry + - nodes[0].best_block_info().1 + - min_cltv_expiry_delta as u32 + - HTLC_FAIL_BACK_BUFFER + - LATENCY_GRACE_PERIOD_BLOCKS + - 1, + ); + connect_blocks( + &nodes[1], + final_max_cltv_expiry + - nodes[1].best_block_info().1 + // Don't expire the path for nodes[1] + - min_cltv_expiry_delta as u32 + - HTLC_FAIL_BACK_BUFFER + - LATENCY_GRACE_PERIOD_BLOCKS + - 1, + ); + connect_blocks(&nodes[2], final_max_cltv_expiry - nodes[2].best_block_info().1); + + let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[2], None, &secp_ctx); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) + .unwrap(); + let release_held_htlc_om = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]).1; + nodes[0] + .onion_messenger + .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + check_added_monitors!(nodes[0], 1); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2]], false); + nodes[2].logger.assert_log_contains( + "lightning::ln::channelmanager", + "violated blinded payment constraints", + 1, + ); +} diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 9708dbd6d88..a760f9573f4 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -107,13 +107,17 @@ pub fn get_blinded_route_parameters( pub fn fail_blinded_htlc_backwards( payment_hash: PaymentHash, intro_node_idx: usize, nodes: &[&Node], + retry_expected: bool ) { for i in (0..nodes.len()).rev() { match i { 0 => { let mut payment_failed_conditions = PaymentFailedConditions::new() .expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]); - expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + if retry_expected { + payment_failed_conditions = payment_failed_conditions.retry_expected(); + } + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); }, i if i <= intro_node_idx => { let unblinded_node_updates = get_htlc_update_msgs!(nodes[i], nodes[i-1].node.get_our_node_id()); @@ -604,7 +608,7 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, if intro_fails { cause_error!(nodes[0], nodes[1], nodes[2], chan_id_1_2, chan_upd_1_2.short_channel_id); check_added_monitors!(nodes[1], 1); - fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]]); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]], false); return } @@ -696,7 +700,7 @@ fn do_blinded_intercept_payment(intercept_node_fails: bool) { expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]); nodes[1].node.process_pending_htlc_forwards(); check_added_monitors!(&nodes[1], 1); - fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]]); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]], false); return } @@ -802,7 +806,7 @@ fn three_hop_blinded_path_fail() { ); nodes[3].node.process_pending_htlc_forwards(); check_added_monitors!(nodes[3], 1); - fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2], &nodes[3]]); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2], &nodes[3]], false); } #[derive(PartialEq)] diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ca5d07cf3cc..b444b9bf46b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2430,6 +2430,9 @@ where /// See `PendingOutboundPayment` documentation for more info. /// /// See `ChannelManager` struct-level documentation for lock order requirements. + #[cfg(test)] + pub(super) pending_outbound_payments: OutboundPayments, + #[cfg(not(test))] pending_outbound_payments: OutboundPayments, /// SCID/SCID Alias -> forward infos. Key of 0 means payments received. @@ -10558,6 +10561,16 @@ where ) } + #[cfg(test)] + pub(super) fn test_create_blinded_payment_paths( + &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, + relative_expiry_seconds: u32 + ) -> Result, ()> { + self.create_blinded_payment_paths( + amount_msats, payment_secret, payment_context, relative_expiry_seconds + ) + } + /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids /// are used when constructing the phantom invoice's route hints. /// diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 3d67a57c837..8edc0bbdf1c 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2494,6 +2494,7 @@ pub struct PaymentFailedConditions<'a> { pub(crate) expected_blamed_scid: Option, pub(crate) expected_blamed_chan_closed: Option, pub(crate) expected_mpp_parts_remain: bool, + pub(crate) retry_expected: bool, } impl<'a> PaymentFailedConditions<'a> { @@ -2503,6 +2504,7 @@ impl<'a> PaymentFailedConditions<'a> { expected_blamed_scid: None, expected_blamed_chan_closed: None, expected_mpp_parts_remain: false, + retry_expected: false, } } pub fn mpp_parts_remain(mut self) -> Self { @@ -2521,6 +2523,10 @@ impl<'a> PaymentFailedConditions<'a> { self.expected_htlc_error_data = Some((code, data)); self } + pub fn retry_expected(mut self) -> Self { + self.retry_expected = true; + self + } } #[cfg(test)] @@ -2586,7 +2592,7 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( }, _ => panic!("Unexpected event"), }; - if !conditions.expected_mpp_parts_remain { + if !conditions.expected_mpp_parts_remain && !conditions.retry_expected { match &payment_failed_events[1] { Event::PaymentFailed { ref payment_hash, ref payment_id, ref reason } => { assert_eq!(*payment_hash, Some(expected_payment_hash), "unexpected second payment_hash"); @@ -2599,6 +2605,11 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( } _ => panic!("Unexpected second event"), } + } else if conditions.retry_expected { + match &payment_failed_events[1] { + Event::PendingHTLCsForwardable { .. } => {}, + _ => panic!("Unexpected second event"), + } } } @@ -2749,8 +2760,12 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret); }, PaymentPurpose::Bolt12OfferPayment { payment_preimage, payment_secret, .. } => { - assert_eq!(expected_preimage, *payment_preimage); - assert_eq!(our_payment_secret.unwrap(), *payment_secret); + if let Some(preimage) = expected_preimage { + assert_eq!(preimage, payment_preimage.unwrap()); + } + if let Some(secret) = our_payment_secret { + assert_eq!(secret, *payment_secret); + } assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret); }, PaymentPurpose::Bolt12RefundPayment { payment_preimage, payment_secret, .. } => { @@ -2772,7 +2787,11 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option } event = Some(events_2[0].clone()); } else if let Some(ref failure) = expected_failure { - assert_eq!(events_2.len(), 2); + // If we successfully decode the HTLC onion but then fail later in + // process_pending_htlc_forwards, then we'll queue the failure and generate a new + // `ProcessPendingHTLCForwards` event. If we fail during the process of decoding the HTLC, + // we'll fail it directly with no intermediate forwarding event. + assert!(events_2.len() == 1 || events_2.len() == 2); expect_htlc_handling_failed_destinations!(events_2, &[failure]); node.node.process_pending_htlc_forwards(); check_added_monitors!(node, 1);