Skip to content

Commit 0850ab9

Browse files
fixup: add test to check what happens to service if client does not claim on time
1 parent b3e0651 commit 0850ab9

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,197 @@ fn late_payment_forwarded_and_safe_after_force_close_does_not_broadcast() {
16121612
}
16131613
}
16141614

1615+
#[test]
1616+
fn htlc_timeout_before_client_claim_results_in_handling_failed() {
1617+
let chanmon_cfgs = create_chanmon_cfgs(3);
1618+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1619+
let mut service_node_config = test_default_channel_config();
1620+
service_node_config.accept_intercept_htlcs = true;
1621+
1622+
let mut client_node_config = test_default_channel_config();
1623+
client_node_config.manually_accept_inbound_channels = true;
1624+
client_node_config.channel_config.accept_underpaying_htlcs = true;
1625+
1626+
let node_chanmgrs = create_node_chanmgrs(
1627+
3,
1628+
&node_cfgs,
1629+
&[Some(service_node_config), Some(client_node_config), None],
1630+
);
1631+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1632+
let (lsps_nodes, promise_secret) = setup_test_lsps2_nodes_with_payer(nodes);
1633+
let LSPSNodesWithPayer { ref service_node, ref client_node, ref payer_node } = lsps_nodes;
1634+
1635+
let payer_node_id = payer_node.node.get_our_node_id();
1636+
let service_node_id = service_node.inner.node.get_our_node_id();
1637+
let client_node_id = client_node.inner.node.get_our_node_id();
1638+
1639+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
1640+
1641+
create_chan_between_nodes_with_value(&payer_node, &service_node.inner, 2_000_000, 100_000);
1642+
1643+
let intercept_scid = service_node.node.get_intercept_scid();
1644+
let user_channel_id = 44u128;
1645+
let cltv_expiry_delta: u32 = 144;
1646+
let payment_size_msat = Some(1_000_000);
1647+
let fee_base_msat: u64 = 10_000;
1648+
1649+
execute_lsps2_dance(
1650+
&lsps_nodes,
1651+
intercept_scid,
1652+
user_channel_id,
1653+
cltv_expiry_delta,
1654+
promise_secret,
1655+
payment_size_msat,
1656+
fee_base_msat,
1657+
);
1658+
1659+
let invoice = create_jit_invoice(
1660+
&client_node,
1661+
service_node_id,
1662+
intercept_scid,
1663+
cltv_expiry_delta,
1664+
payment_size_msat,
1665+
"timeout-before-claim",
1666+
3600,
1667+
)
1668+
.unwrap();
1669+
1670+
payer_node
1671+
.node
1672+
.pay_for_bolt11_invoice(
1673+
&invoice,
1674+
PaymentId(invoice.payment_hash().to_byte_array()),
1675+
None,
1676+
Default::default(),
1677+
Retry::Attempts(3),
1678+
)
1679+
.unwrap();
1680+
1681+
check_added_monitors!(payer_node, 1);
1682+
let events = payer_node.node.get_and_clear_pending_msg_events();
1683+
let ev = SendEvent::from_event(events[0].clone());
1684+
service_node.inner.node.handle_update_add_htlc(payer_node_id, &ev.msgs[0]);
1685+
do_commitment_signed_dance(&service_node.inner, &payer_node, &ev.commitment_msg, false, true);
1686+
service_node.inner.node.process_pending_htlc_forwards();
1687+
1688+
let events = service_node.inner.node.get_and_clear_pending_events();
1689+
assert_eq!(events.len(), 1);
1690+
match &events[0] {
1691+
Event::HTLCIntercepted {
1692+
intercept_id,
1693+
requested_next_hop_scid,
1694+
payment_hash: _,
1695+
expected_outbound_amount_msat,
1696+
..
1697+
} => {
1698+
assert_eq!(*requested_next_hop_scid, intercept_scid);
1699+
service_handler
1700+
.htlc_intercepted(
1701+
*requested_next_hop_scid,
1702+
*intercept_id,
1703+
*expected_outbound_amount_msat,
1704+
PaymentHash(invoice.payment_hash().to_byte_array()),
1705+
)
1706+
.unwrap();
1707+
},
1708+
other => panic!("Expected HTLCIntercepted, got {:?}", other),
1709+
}
1710+
1711+
// Create and mark broadcast safe so the channel is fully ready
1712+
let expected_outbound_amount_msat = payment_size_msat.unwrap() - fee_base_msat;
1713+
let (channel_id, _funding_tx) = create_channel_with_manual_broadcast(
1714+
&service_node_id,
1715+
&client_node_id,
1716+
&service_node,
1717+
&client_node,
1718+
user_channel_id,
1719+
&expected_outbound_amount_msat,
1720+
true,
1721+
);
1722+
1723+
service_handler.channel_ready(user_channel_id, &channel_id, &client_node_id).unwrap();
1724+
service_node.inner.node.process_pending_htlc_forwards();
1725+
1726+
// Forward to client, but do not claim yet
1727+
let pay_event = {
1728+
{
1729+
let mut added_monitors =
1730+
service_node.inner.chain_monitor.added_monitors.lock().unwrap();
1731+
assert_eq!(added_monitors.len(), 1);
1732+
added_monitors.clear();
1733+
}
1734+
let mut msg_events = service_node.inner.node.get_and_clear_pending_msg_events();
1735+
assert_eq!(msg_events.len(), 1);
1736+
SendEvent::from_event(msg_events.remove(0))
1737+
};
1738+
1739+
client_node.inner.node.handle_update_add_htlc(service_node_id, &pay_event.msgs[0]);
1740+
do_commitment_signed_dance(
1741+
&client_node.inner,
1742+
&service_node.inner,
1743+
&pay_event.commitment_msg,
1744+
false,
1745+
true,
1746+
);
1747+
client_node.inner.node.process_pending_htlc_forwards();
1748+
1749+
let client_events = client_node.inner.node.get_and_clear_pending_events();
1750+
assert_eq!(client_events.len(), 1);
1751+
let preimage = match &client_events[0] {
1752+
Event::PaymentClaimable { purpose, .. } => purpose.preimage().unwrap(),
1753+
other => panic!("Expected PaymentClaimable, got {:?}", other),
1754+
};
1755+
1756+
// Advance blocks past CLTV expiry before the client attempts to claim
1757+
const SOME_EXTRA_BLOCKS: u32 = 3;
1758+
let client_htlc_cltv_expiry = pay_event.msgs[0].cltv_expiry;
1759+
let target_height = client_htlc_cltv_expiry.saturating_add(SOME_EXTRA_BLOCKS);
1760+
let cur_height = service_node.inner.best_block_info().1;
1761+
let d = target_height - cur_height;
1762+
connect_blocks(&service_node.inner, d);
1763+
connect_blocks(&client_node.inner, d);
1764+
connect_blocks(&payer_node, d);
1765+
1766+
service_node.inner.node.process_pending_htlc_forwards();
1767+
client_node.inner.node.process_pending_htlc_forwards();
1768+
1769+
// Service->client channel should close due to HTLC timeout
1770+
let svc_events = service_node.inner.node.get_and_clear_pending_events();
1771+
let closed_on_service = svc_events.iter().any(|ev| {
1772+
matches!(ev, Event::ChannelClosed { reason: ClosureReason::HTLCsTimedOut { .. }, .. })
1773+
});
1774+
assert!(closed_on_service, "Expected service->client channel to close due to HTLC timeout");
1775+
1776+
// Client tries to claim but should fail since HTLC timed out
1777+
client_node.inner.node.claim_funds(preimage);
1778+
let client_events = client_node.inner.node.get_and_clear_pending_events();
1779+
assert_eq!(client_events.len(), 1);
1780+
match &client_events[0] {
1781+
Event::HTLCHandlingFailed { failure_type, .. } => match failure_type {
1782+
lightning::events::HTLCHandlingFailureType::Receive { payment_hash } => {
1783+
assert_eq!(*payment_hash, PaymentHash(invoice.payment_hash().to_byte_array()));
1784+
},
1785+
_ => panic!("Unexpected failure_type: {:?}", failure_type),
1786+
},
1787+
other => panic!("Expected HTLCHandlingFailed after timeout, got {:?}", other),
1788+
}
1789+
1790+
// Payer->service channel should remain open
1791+
{
1792+
let chans = service_node.inner.node.list_channels();
1793+
assert!(chans
1794+
.iter()
1795+
.any(|cd| cd.counterparty.node_id == payer_node_id && cd.is_channel_ready));
1796+
}
1797+
1798+
service_node.inner.node.get_and_clear_pending_msg_events();
1799+
client_node.inner.node.get_and_clear_pending_msg_events();
1800+
payer_node.node.get_and_clear_pending_msg_events();
1801+
service_node.inner.chain_monitor.added_monitors.lock().unwrap().clear();
1802+
client_node.inner.chain_monitor.added_monitors.lock().unwrap().clear();
1803+
payer_node.chain_monitor.added_monitors.lock().unwrap().clear();
1804+
}
1805+
16151806
fn claim_and_assert_forwarded_only<'a, 'b, 'c>(
16161807
payer_node: &lightning::ln::functional_test_utils::Node<'a, 'b, 'c>,
16171808
service_node: &lightning::ln::functional_test_utils::Node<'a, 'b, 'c>,

0 commit comments

Comments
 (0)