Automatically fail intercepts back on timeout
authorValentine Wallace <vwallace@protonmail.com>
Mon, 14 Nov 2022 20:05:37 +0000 (15:05 -0500)
committerValentine Wallace <vwallace@protonmail.com>
Wed, 30 Nov 2022 17:52:23 +0000 (12:52 -0500)
lightning/src/ln/channelmanager.rs
lightning/src/ln/payment_tests.rs

index 3b1bf7432fbf42b69ba4934993f48d4593c4972e..f27ca53bcf07a3c42334ee0b8cec05e681cdfc34 100644 (file)
@@ -3067,6 +3067,9 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
        /// Note that LDK does not enforce fee requirements in `amt_to_forward_msat`, and will not stop
        /// you from forwarding more than you received.
        ///
+       /// Errors if the event was not handled in time, in which case the HTLC was automatically failed
+       /// backwards.
+       ///
        /// [`UserConfig::accept_intercept_htlcs`]: crate::util::config::UserConfig::accept_intercept_htlcs
        /// [`HTLCIntercepted`]: events::Event::HTLCIntercepted
        // TODO: when we move to deciding the best outbound channel at forward time, only take
@@ -3109,6 +3112,9 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
        /// Fails the intercepted HTLC indicated by intercept_id. Should only be called in response to
        /// an [`HTLCIntercepted`] event. See [`ChannelManager::forward_intercepted_htlc`].
        ///
+       /// Errors if the event was not handled in time, in which case the HTLC was automatically failed
+       /// backwards.
+       ///
        /// [`HTLCIntercepted`]: events::Event::HTLCIntercepted
        pub fn fail_intercepted_htlc(&self, intercept_id: InterceptId) -> Result<(), APIError> {
                let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
@@ -6256,7 +6262,6 @@ where
                                        if height >= htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER {
                                                let mut htlc_msat_height_data = byte_utils::be64_to_array(htlc.value).to_vec();
                                                htlc_msat_height_data.extend_from_slice(&byte_utils::be32_to_array(height));
-
                                                timed_out_htlcs.push((HTLCSource::PreviousHopData(htlc.prev_hop.clone()), payment_hash.clone(), HTLCFailReason::Reason {
                                                        failure_code: 0x4000 | 15,
                                                        data: htlc_msat_height_data
@@ -6266,6 +6271,29 @@ where
                                });
                                !htlcs.is_empty() // Only retain this entry if htlcs has at least one entry.
                        });
+
+                       let mut intercepted_htlcs = self.pending_intercepted_htlcs.lock().unwrap();
+                       intercepted_htlcs.retain(|_, htlc| {
+                               if height >= htlc.forward_info.outgoing_cltv_value - HTLC_FAIL_BACK_BUFFER {
+                                       let prev_hop_data = HTLCSource::PreviousHopData(HTLCPreviousHopData {
+                                               short_channel_id: htlc.prev_short_channel_id,
+                                               htlc_id: htlc.prev_htlc_id,
+                                               incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret,
+                                               phantom_shared_secret: None,
+                                               outpoint: htlc.prev_funding_outpoint,
+                                       });
+
+                                       let requested_forward_scid /* intercept scid */ = match htlc.forward_info.routing {
+                                               PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id,
+                                               _ => unreachable!(),
+                                       };
+                                       timed_out_htlcs.push((prev_hop_data, htlc.forward_info.payment_hash,
+                                                       HTLCFailReason::Reason { failure_code: 0x2000 | 2, data: Vec::new() },
+                                                       HTLCDestination::InvalidForward { requested_forward_scid }));
+                                       log_trace!(self.logger, "Timing out intercepted HTLC with requested forward scid {}", requested_forward_scid);
+                                       false
+                               } else { true }
+                       });
                }
 
                self.handle_init_event_channel_failures(failed_channels);
index a876b73b88a8f5fb02cc9746200acd824d733347..ead5bcbd4694cf95c93abc717dfd633b7071e55a 100644 (file)
@@ -16,7 +16,7 @@ use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS
 use crate::chain::transaction::OutPoint;
 use crate::chain::keysinterface::KeysInterface;
 use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
-use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, InterceptId, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
+use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
 use crate::ln::msgs;
 use crate::ln::msgs::ChannelMessageHandler;
 use crate::routing::gossip::RoutingFees;
@@ -1243,6 +1243,13 @@ fn abandoned_send_payment_idempotent() {
        claim_payment(&nodes[0], &[&nodes[1]], second_payment_preimage);
 }
 
+#[derive(PartialEq)]
+enum InterceptTest {
+       Forward,
+       Fail,
+       Timeout,
+}
+
 #[test]
 fn test_trivial_inflight_htlc_tracking(){
        // In this test, we test three scenarios:
@@ -1378,11 +1385,13 @@ 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 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);
+       do_test_intercepted_payment(InterceptTest::Forward);
+       do_test_intercepted_payment(InterceptTest::Fail);
+       // Make sure that intercepted payments will be automatically failed back if too many blocks pass.
+       do_test_intercepted_payment(InterceptTest::Timeout);
 }
 
-fn do_test_intercepted_payment(fail_intercept: bool) {
+fn do_test_intercepted_payment(test: InterceptTest) {
        let chanmon_cfgs = create_chanmon_cfgs(3);
        let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
 
@@ -1459,7 +1468,7 @@ fn do_test_intercepted_payment(fail_intercept: bool) {
        let unknown_chan_id_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &[42; 32], nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
        assert_eq!(unknown_chan_id_err , APIError::APIMisuseError { err: format!("Channel with id {:?} not found", [42; 32]) });
 
-       if fail_intercept {
+       if test == InterceptTest::Fail {
                // 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 }]);
@@ -1477,15 +1486,10 @@ fn do_test_intercepted_payment(fail_intercept: bool) {
                        .blamed_chan_closed(true)
                        .expected_htlc_error_data(0x4000 | 10, &[]);
                expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions);
-       } else {
+       } else if test == InterceptTest::Forward {
                // Open the just-in-time channel so the payment can then be forwarded.
                let (_, channel_id) = open_zero_conf_channel(&nodes[1], &nodes[2], None);
 
-               // Check for unknown intercept id error.
-               let unknown_intercept_id = InterceptId([42; 32]);
-               let unknown_intercept_id_err = nodes[1].node.forward_intercepted_htlc(unknown_intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
-               assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {:?} not found", unknown_intercept_id.0) });
-
                // Finally, forward the intercepted payment through and claim it.
                nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap();
                expect_pending_htlcs_forwardable!(nodes[1]);
@@ -1523,5 +1527,34 @@ fn do_test_intercepted_payment(fail_intercept: bool) {
                        },
                        _ => panic!("Unexpected event")
                }
+       } else if test == InterceptTest::Timeout {
+               let mut block = Block {
+                       header: BlockHeader { version: 0x20000000, prev_blockhash: nodes[0].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 },
+                       txdata: vec![],
+               };
+               connect_block(&nodes[0], &block);
+               connect_block(&nodes[1], &block);
+               let block_count = 183; // find_route adds a random CLTV offset, so hardcode rather than summing consts
+               for _ in 0..block_count {
+                       block.header.prev_blockhash = block.block_hash();
+                       connect_block(&nodes[0], &block);
+                       connect_block(&nodes[1], &block);
+               }
+               expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::InvalidForward { requested_forward_scid: intercept_scid }]);
+               check_added_monitors!(nodes[1], 1);
+               let htlc_timeout_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+               assert!(htlc_timeout_updates.update_add_htlcs.is_empty());
+               assert_eq!(htlc_timeout_updates.update_fail_htlcs.len(), 1);
+               assert!(htlc_timeout_updates.update_fail_malformed_htlcs.is_empty());
+               assert!(htlc_timeout_updates.update_fee.is_none());
+
+               nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &htlc_timeout_updates.update_fail_htlcs[0]);
+               commitment_signed_dance!(nodes[0], nodes[1], htlc_timeout_updates.commitment_signed, false);
+               expect_payment_failed!(nodes[0], payment_hash, false, 0x2000 | 2, []);
+
+               // Check for unknown intercept id error.
+               let (_, channel_id) = open_zero_conf_channel(&nodes[1], &nodes[2], None);
+               let unknown_intercept_id_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
+               assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {:?} not found", intercept_id.0) });
        }
 }