From: Matt Corallo Date: Sun, 30 Oct 2022 00:20:52 +0000 (+0000) Subject: Test that PaymentIds are idempotency keys until `abandon_payment` X-Git-Tag: v0.0.113~58^2~2 X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=0ae45a2578b41fde39ba9bb5e6e33af62b319f04;p=rust-lightning Test that PaymentIds are idempotency keys until `abandon_payment` --- diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index b803ad1fd..75823fd53 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -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 { 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) { diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 83eb84334..ca2208ce1 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -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); +}