X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fchanmon_update_fail_tests.rs;fp=lightning%2Fsrc%2Fln%2Fchanmon_update_fail_tests.rs;h=2918b4cef13aaf2baa2c83c280aacc6c075f2fae;hb=7e78fa660cec8a73286c94c1073ee588140e7a01;hp=2c62abc55f85c00defe3f608eb6dba56824a6a23;hpb=fecac81874c41724a855c9e696c312353609df23;p=rust-lightning diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index 2c62abc5..2918b4ce 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -2215,3 +2215,119 @@ fn channel_holding_cell_serialize() { do_channel_holding_cell_serialize(true, false); do_channel_holding_cell_serialize(false, true); // last arg doesn't matter } + +#[derive(PartialEq)] +enum HTLCStatusAtDupClaim { + Received, + HoldingCell, + Cleared, +} +fn do_test_reconnect_dup_htlc_claims(htlc_status: HTLCStatusAtDupClaim, second_fails: bool) { + // When receiving an update_fulfill_htlc message, we immediately forward the claim backwards + // along the payment path before waiting for a full commitment_signed dance. This is great, but + // can cause duplicative claims if a node sends an update_fulfill_htlc message, disconnects, + // reconnects, and then has to re-send its update_fulfill_htlc message again. + // In previous code, we didn't handle the double-claim correctly, spuriously closing the + // channel on which the inbound HTLC was received. + 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(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()); + let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known()).2; + + let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000); + + let mut as_raa = None; + if htlc_status == HTLCStatusAtDupClaim::HoldingCell { + // In order to get the HTLC claim into the holding cell at nodes[1], we need nodes[1] to be + // awaiting a remote revoke_and_ack from nodes[0]. + let (_, second_payment_hash, second_payment_secret) = get_payment_preimage_hash!(nodes[1]); + let route = get_route(&nodes[0].node.get_our_node_id(), &nodes[0].net_graph_msg_handler.network_graph.read().unwrap(), + &nodes[1].node.get_our_node_id(), Some(InvoiceFeatures::known()), None, &Vec::new(), 100_000, TEST_FINAL_CLTV, nodes[1].logger).unwrap(); + nodes[0].node.send_payment(&route, second_payment_hash, &Some(second_payment_secret)).unwrap(); + check_added_monitors!(nodes[0], 1); + + let send_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(), &send_event.msgs[0]); + nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &send_event.commitment_msg); + check_added_monitors!(nodes[1], 1); + + let (bs_raa, bs_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_raa); + check_added_monitors!(nodes[0], 1); + nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_cs); + check_added_monitors!(nodes[0], 1); + + as_raa = Some(get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id())); + } + + let fulfill_msg = msgs::UpdateFulfillHTLC { + channel_id: chan_2, + htlc_id: 0, + payment_preimage, + }; + if second_fails { + assert!(nodes[2].node.fail_htlc_backwards(&payment_hash)); + expect_pending_htlcs_forwardable!(nodes[2]); + check_added_monitors!(nodes[2], 1); + get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + } else { + assert!(nodes[2].node.claim_funds(payment_preimage)); + check_added_monitors!(nodes[2], 1); + let cs_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + assert_eq!(cs_updates.update_fulfill_htlcs.len(), 1); + // Check that the message we're about to deliver matches the one generated: + assert_eq!(fulfill_msg, cs_updates.update_fulfill_htlcs[0]); + } + nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &fulfill_msg); + check_added_monitors!(nodes[1], 1); + + let mut bs_updates = None; + if htlc_status != HTLCStatusAtDupClaim::HoldingCell { + bs_updates = Some(get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id())); + assert_eq!(bs_updates.as_ref().unwrap().update_fulfill_htlcs.len(), 1); + nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.as_ref().unwrap().update_fulfill_htlcs[0]); + expect_payment_sent!(nodes[0], payment_preimage); + if htlc_status == HTLCStatusAtDupClaim::Cleared { + commitment_signed_dance!(nodes[0], nodes[1], &bs_updates.as_ref().unwrap().commitment_signed, false); + } + } else { + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + } + + nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id(), false); + nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false); + + if second_fails { + reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (0, 0), (1, 0), (0, 0), (0, 0), (false, false)); + expect_pending_htlcs_forwardable!(nodes[1]); + } else { + reconnect_nodes(&nodes[1], &nodes[2], (false, false), (0, 0), (1, 0), (0, 0), (0, 0), (0, 0), (false, false)); + } + + if htlc_status == HTLCStatusAtDupClaim::HoldingCell { + nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_raa.unwrap()); + check_added_monitors!(nodes[1], 1); + expect_pending_htlcs_forwardable_ignore!(nodes[1]); // We finally receive the second payment, but don't claim it + + bs_updates = Some(get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id())); + assert_eq!(bs_updates.as_ref().unwrap().update_fulfill_htlcs.len(), 1); + nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.as_ref().unwrap().update_fulfill_htlcs[0]); + expect_payment_sent!(nodes[0], payment_preimage); + } + if htlc_status != HTLCStatusAtDupClaim::Cleared { + commitment_signed_dance!(nodes[0], nodes[1], &bs_updates.as_ref().unwrap().commitment_signed, false); + } +} + +#[test] +fn test_reconnect_dup_htlc_claims() { + do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Received, false); + do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::HoldingCell, false); + do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Cleared, false); + do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Received, true); + do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::HoldingCell, true); + do_test_reconnect_dup_htlc_claims(HTLCStatusAtDupClaim::Cleared, true); +}