Test that PaymentIds are idempotency keys until `abandon_payment`
authorMatt Corallo <git@bluematt.me>
Sun, 30 Oct 2022 00:20:52 +0000 (00:20 +0000)
committerMatt Corallo <git@bluematt.me>
Wed, 2 Nov 2022 01:09:07 +0000 (01:09 +0000)
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/payment_tests.rs

index b803ad1fdbd0b116ee7893f9a52ac45f1919729d..75823fd53f8e5d1dc2cd229333b8ea7556b9f44f 100644 (file)
@@ -1905,6 +1905,22 @@ pub fn fail_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe
 }
 
 pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths_slice: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_hash: PaymentHash) {
+       let expected_payment_id = pass_failed_payment_back_no_abandon(origin_node, expected_paths_slice, skip_last, our_payment_hash);
+       if !skip_last {
+               origin_node.node.abandon_payment(expected_payment_id.unwrap());
+               let events = origin_node.node.get_and_clear_pending_events();
+               assert_eq!(events.len(), 1);
+               match events[0] {
+                       Event::PaymentFailed { ref payment_hash, ref payment_id } => {
+                               assert_eq!(*payment_hash, our_payment_hash, "unexpected second payment_hash");
+                               assert_eq!(*payment_id, expected_payment_id.unwrap());
+                       }
+                       _ => panic!("Unexpected second event"),
+               }
+       }
+}
+
+pub fn pass_failed_payment_back_no_abandon<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths_slice: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_hash: PaymentHash) -> Option<PaymentId> {
        let mut expected_paths: Vec<_> = expected_paths_slice.iter().collect();
        check_added_monitors!(expected_paths[0].last().unwrap(), expected_paths.len());
 
@@ -1928,6 +1944,8 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe
        per_path_msgs.sort_unstable_by(|(_, node_id_a), (_, node_id_b)| node_id_a.cmp(node_id_b));
        expected_paths.sort_unstable_by(|path_a, path_b| path_a[path_a.len() - 2].node.get_our_node_id().cmp(&path_b[path_b.len() - 2].node.get_our_node_id()));
 
+       let mut expected_payment_id = None;
+
        for (i, (expected_route, (path_msgs, next_hop))) in expected_paths.iter().zip(per_path_msgs.drain(..)).enumerate() {
                let mut next_msgs = Some(path_msgs);
                let mut expected_next_node = next_hop;
@@ -1976,7 +1994,7 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe
                        commitment_signed_dance!(origin_node, prev_node, next_msgs.as_ref().unwrap().1, false);
                        let events = origin_node.node.get_and_clear_pending_events();
                        assert_eq!(events.len(), 1);
-                       let expected_payment_id = match events[0] {
+                       expected_payment_id = Some(match events[0] {
                                Event::PaymentPathFailed { payment_hash, payment_failed_permanently, all_paths_failed, ref path, ref payment_id, .. } => {
                                        assert_eq!(payment_hash, our_payment_hash);
                                        assert!(payment_failed_permanently);
@@ -1987,19 +2005,7 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe
                                        payment_id.unwrap()
                                },
                                _ => panic!("Unexpected event"),
-                       };
-                       if i == expected_paths.len() - 1 {
-                               origin_node.node.abandon_payment(expected_payment_id);
-                               let events = origin_node.node.get_and_clear_pending_events();
-                               assert_eq!(events.len(), 1);
-                               match events[0] {
-                                       Event::PaymentFailed { ref payment_hash, ref payment_id } => {
-                                               assert_eq!(*payment_hash, our_payment_hash, "unexpected second payment_hash");
-                                               assert_eq!(*payment_id, expected_payment_id);
-                                       }
-                                       _ => panic!("Unexpected second event"),
-                               }
-                       }
+                       });
                }
        }
 
@@ -2008,6 +2014,8 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe
        assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_events().is_empty());
        assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_msg_events().is_empty());
        check_added_monitors!(expected_paths[0].last().unwrap(), 0);
+
+       expected_payment_id
 }
 
 pub fn fail_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], our_payment_hash: PaymentHash)  {
index 83eb84334a0720671f83cfc419134598ce73a189..ca2208ce106f6e39ba89b444e424b6dbbc32bd56 100644 (file)
@@ -1326,3 +1326,62 @@ fn claimed_send_payment_idempotent() {
        pass_along_route(&nodes[0], &[&[&nodes[1]]], 100_000, second_payment_hash, second_payment_secret);
        claim_payment(&nodes[0], &[&nodes[1]], second_payment_preimage);
 }
+
+#[test]
+fn abandoned_send_payment_idempotent() {
+       // Tests that `send_payment` (and friends) allow duplicate PaymentIds immediately after
+       // abandon_payment.
+       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(&nodes, 0, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2;
+
+       let (route, second_payment_hash, second_payment_preimage, second_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
+       let (_, first_payment_hash, _, payment_id) = send_along_route(&nodes[0], route.clone(), &[&nodes[1]], 100_000);
+
+       macro_rules! check_send_rejected {
+               () => {
+                       // If we try to resend a new payment with a different payment_hash but with the same
+                       // payment_id, it should be rejected.
+                       let send_result = nodes[0].node.send_payment(&route, second_payment_hash, &Some(second_payment_secret), payment_id);
+                       match send_result {
+                               Err(PaymentSendFailure::ParameterError(APIError::RouteError { err: "Payment already in progress" })) => {},
+                               _ => panic!("Unexpected send result: {:?}", send_result),
+                       }
+
+                       // Further, if we try to send a spontaneous payment with the same payment_id it should
+                       // also be rejected.
+                       let send_result = nodes[0].node.send_spontaneous_payment(&route, None, payment_id);
+                       match send_result {
+                               Err(PaymentSendFailure::ParameterError(APIError::RouteError { err: "Payment already in progress" })) => {},
+                               _ => panic!("Unexpected send result: {:?}", send_result),
+                       }
+               }
+       }
+
+       check_send_rejected!();
+
+       nodes[1].node.fail_htlc_backwards(&first_payment_hash);
+       expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], [HTLCDestination::FailedPayment { payment_hash: first_payment_hash }]);
+
+       pass_failed_payment_back_no_abandon(&nodes[0], &[&[&nodes[1]]], false, first_payment_hash);
+       check_send_rejected!();
+
+       // Until we abandon the payment, no matter how many timer ticks pass, we still cannot reuse the
+       // PaymentId.
+       for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS {
+               nodes[0].node.timer_tick_occurred();
+       }
+       check_send_rejected!();
+
+       nodes[0].node.abandon_payment(payment_id);
+       get_event!(nodes[0], Event::PaymentFailed);
+
+       // However, we can reuse the PaymentId immediately after we `abandon_payment`.
+       nodes[0].node.send_payment(&route, second_payment_hash, &Some(second_payment_secret), payment_id).unwrap();
+       check_added_monitors!(nodes[0], 1);
+       pass_along_route(&nodes[0], &[&[&nodes[1]]], 100_000, second_payment_hash, second_payment_secret);
+       claim_payment(&nodes[0], &[&nodes[1]], second_payment_preimage);
+}