From 5e8146c77c78ebc52f33bf96b3e866dbe1bf4c96 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 7 Nov 2022 11:16:49 -0500 Subject: [PATCH] Allow failing back intercepted HTLCs Co-authored-by: John Cantrell Co-authored-by: Valentine Wallace --- lightning/src/ln/channelmanager.rs | 32 ++++++++- lightning/src/ln/payment_tests.rs | 106 ++++++++++++++++++----------- lightning/src/util/events.rs | 6 ++ 3 files changed, 103 insertions(+), 41 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a80cfbd3c20..7c83dde404b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -3165,7 +3165,8 @@ impl ChannelManager Result<(), APIError> { @@ -3199,6 +3200,35 @@ impl ChannelManager Result<(), APIError> { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); + + let payment = self.pending_intercepted_htlcs.lock().unwrap().remove(&intercept_id) + .ok_or_else(|| APIError::APIMisuseError { + err: format!("Payment with InterceptId {:?} not found", intercept_id) + })?; + + if let PendingHTLCRouting::Forward { short_channel_id, .. } = payment.forward_info.routing { + let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData { + short_channel_id: payment.prev_short_channel_id, + outpoint: payment.prev_funding_outpoint, + htlc_id: payment.prev_htlc_id, + incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret, + phantom_shared_secret: None, + }); + + let failure_reason = HTLCFailReason::Reason { failure_code: 0x4000 | 10, data: Vec::new() }; + let destination = HTLCDestination::UnknownNextHop { requested_forward_scid: short_channel_id }; + self.fail_htlc_backwards_internal(htlc_source, &payment.forward_info.payment_hash, failure_reason, destination); + } else { unreachable!() } + + Ok(()) + } + /// Processes HTLCs which are pending waiting on random forward delay. /// /// Should only really ever be called in response to a PendingHTLCsForwardable event. diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index b06984a4da9..3aaef5d0b8c 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -1388,9 +1388,15 @@ fn abandoned_send_payment_idempotent() { } #[test] -fn forward_intercepted_payment() { +fn intercepted_payment() { // Test that detecting an intercept scid on payment forward will signal LDK to generate an - // intercept event, which the LSP can then use to open a JIT channel to forward the payment. + // intercept event, which the LSP can then use to either (a) open a JIT channel to forward the + // payment or (b) fail the payment. + do_test_intercepted_payment(false); + do_test_intercepted_payment(true); +} + +fn do_test_intercepted_payment(fail_intercept: bool) { 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]); @@ -1457,44 +1463,64 @@ fn forward_intercepted_payment() { _ => panic!() }; - // Open the just-in-time channel so the payment can then be forwarded. - let scid = create_announced_chan_between_nodes(&nodes, 1, 2, channelmanager::provided_init_features(), channelmanager::provided_init_features()).0.contents.short_channel_id; - - // Finally, forward the intercepted payment through and claim it. - nodes[1].node.forward_intercepted_htlc(intercept_id, scid, expected_outbound_amount_msat).unwrap(); - expect_pending_htlcs_forwardable!(nodes[1]); - - let payment_event = { - { - let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - added_monitors.clear(); + if fail_intercept { + // Ensure we can fail the intercepted payment back. + 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); + + // Ensure the payment fails with the expected error. + let mut fail_conditions = PaymentFailedConditions::new() + .blamed_scid(intercept_scid) + .blamed_chan_closed(true) + .expected_htlc_error_data(0x4000 | 10, &[]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions); + } else { + // Open the just-in-time channel so the payment can then be forwarded. + let scid = create_announced_chan_between_nodes(&nodes, 1, 2, channelmanager::provided_init_features(), channelmanager::provided_init_features()).0.contents.short_channel_id; + + // Finally, forward the intercepted payment through and claim it. + nodes[1].node.forward_intercepted_htlc(intercept_id, scid, expected_outbound_amount_msat).unwrap(); + expect_pending_htlcs_forwardable!(nodes[1]); + + let payment_event = { + { + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + added_monitors.clear(); + } + let mut events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + SendEvent::from_event(events.remove(0)) + }; + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); + commitment_signed_dance!(nodes[2], nodes[1], &payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[2]); + + let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap(); + expect_payment_received!(&nodes[2], payment_hash, payment_secret, amt_msat, Some(payment_preimage)); + do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[1], &nodes[2])[..]), false, payment_preimage); + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + match events[0] { + Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => { + assert_eq!(payment_preimage, *ev_preimage); + assert_eq!(payment_hash, *ev_hash); + assert_eq!(fee_paid_msat, &Some(1000)); + }, + _ => panic!("Unexpected event") + } + match events[1] { + Event::PaymentPathSuccessful { payment_hash: hash, .. } => { + assert_eq!(hash, Some(payment_hash)); + }, + _ => panic!("Unexpected event") } - let mut events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 1); - SendEvent::from_event(events.remove(0)) - }; - nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); - commitment_signed_dance!(nodes[2], nodes[1], &payment_event.commitment_msg, false, true); - expect_pending_htlcs_forwardable!(nodes[2]); - - let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap(); - expect_payment_received!(&nodes[2], payment_hash, payment_secret, amt_msat, Some(payment_preimage)); - do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[1], &nodes[2])[..]), false, payment_preimage); - let events = nodes[0].node.get_and_clear_pending_events(); - assert_eq!(events.len(), 2); - match events[0] { - Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => { - assert_eq!(payment_preimage, *ev_preimage); - assert_eq!(payment_hash, *ev_hash); - assert_eq!(fee_paid_msat, &Some(1000)); - }, - _ => panic!("Unexpected event") - } - match events[1] { - Event::PaymentPathSuccessful { payment_hash: hash, .. } => { - assert_eq!(hash, Some(payment_hash)); - }, - _ => panic!("Unexpected event") } } diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index 35307b6f59f..408b8bf9481 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -582,7 +582,13 @@ pub enum Event { /// you've encoded an intercept scid in the receiver's invoice route hints using /// [`ChannelManager::get_intercept_scid`]. /// + /// [`ChannelManager::forward_intercepted_htlc`] or + /// [`ChannelManager::fail_intercepted_htlc`] MUST be called in response to this event. See + /// their docs for more information. + /// /// [`ChannelManager::get_intercept_scid`]: crate::ln::channelmanager::ChannelManager::get_intercept_scid + /// [`ChannelManager::forward_intercepted_htlc`]: crate::ln::channelmanager::ChannelManager::forward_intercepted_htlc + /// [`ChannelManager::fail_intercepted_htlc`]: crate::ln::channelmanager::ChannelManager::fail_intercepted_htlc HTLCIntercepted { /// The fake scid that was programmed as the next hop's scid, generated using /// [`ChannelManager::get_intercept_scid`].