Skip to content

Commit 25ab3bc

Browse files
committed
Add LSPS2 BOLT12 end-to-end integration test
Exercise the full flow through onion-message invoice exchange, , JIT channel opening, and settlement to confirm paths integrate with LSPS2 service handling. Co-Authored-By: HAL 9000
1 parent 0dd1962 commit 25ab3bc

File tree

1 file changed

+265
-1
lines changed

1 file changed

+265
-1
lines changed

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 265 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ use lightning::ln::functional_test_utils::*;
1414
use lightning::ln::msgs::BaseMessageHandler;
1515
use lightning::ln::msgs::ChannelMessageHandler;
1616
use lightning::ln::msgs::MessageSendEvent;
17+
use lightning::ln::msgs::OnionMessageHandler;
1718
use lightning::ln::types::ChannelId;
1819
use lightning::offers::invoice_request::InvoiceRequestFields;
1920
use lightning::offers::offer::OfferId;
2021
use lightning::routing::router::{InFlightHtlcs, Route, RouteParameters, Router};
21-
use lightning::sign::ReceiveAuthKey;
22+
use lightning::sign::{RandomBytes, ReceiveAuthKey};
2223

2324
use lightning_liquidity::events::LiquidityEvent;
2425
use lightning_liquidity::lsps0::ser::LSPSDateTime;
@@ -1616,6 +1617,269 @@ fn bolt12_custom_router_uses_lsps2_intercept_scid() {
16161617
assert_eq!(*lookup.short_channel_id.lock().unwrap(), Some(intercept_scid));
16171618
}
16181619

1620+
#[test]
1621+
fn bolt12_lsps2_end_to_end_test() {
1622+
// End-to-end test of the BOLT12 + LSPS2 JIT channel flow. Three nodes: payer, service, client.
1623+
// client_trusts_lsp=true; funding transaction broadcast happens after client claims the HTLC.
1624+
let chanmon_cfgs = create_chanmon_cfgs(3);
1625+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1626+
1627+
let mut service_node_config = test_default_channel_config();
1628+
service_node_config.htlc_interception_flags = HTLCInterceptionFlags::ToInterceptSCIDs as u8;
1629+
1630+
let mut client_node_config = test_default_channel_config();
1631+
client_node_config.accept_inbound_channels = true;
1632+
client_node_config.channel_config.accept_underpaying_htlcs = true;
1633+
1634+
let node_chanmgrs = create_node_chanmgrs(
1635+
3,
1636+
&node_cfgs,
1637+
&[Some(service_node_config), Some(client_node_config), None],
1638+
);
1639+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1640+
let (lsps_nodes, promise_secret) = setup_test_lsps2_nodes_with_payer(nodes);
1641+
let LSPSNodesWithPayer { ref service_node, ref client_node, ref payer_node } = lsps_nodes;
1642+
1643+
let payer_node_id = payer_node.node.get_our_node_id();
1644+
let service_node_id = service_node.inner.node.get_our_node_id();
1645+
let client_node_id = client_node.inner.node.get_our_node_id();
1646+
1647+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
1648+
1649+
create_chan_between_nodes_with_value(&payer_node, &service_node.inner, 2_000_000, 100_000);
1650+
1651+
let intercept_scid = service_node.node.get_intercept_scid();
1652+
let user_channel_id = 42;
1653+
let cltv_expiry_delta: u32 = 144;
1654+
let payment_size_msat = Some(1_000_000);
1655+
let fee_base_msat = 1_000;
1656+
1657+
execute_lsps2_dance(
1658+
&lsps_nodes,
1659+
intercept_scid,
1660+
user_channel_id,
1661+
cltv_expiry_delta,
1662+
promise_secret,
1663+
payment_size_msat,
1664+
fee_base_msat,
1665+
);
1666+
1667+
// Disconnect payer from client to ensure deterministic onion message routing through service.
1668+
payer_node.node.peer_disconnected(client_node_id);
1669+
client_node.node.peer_disconnected(payer_node_id);
1670+
payer_node.onion_messenger.peer_disconnected(client_node_id);
1671+
client_node.onion_messenger.peer_disconnected(payer_node_id);
1672+
1673+
#[cfg(c_bindings)]
1674+
let offer = {
1675+
let mut offer_builder = client_node.node.create_offer_builder().unwrap();
1676+
offer_builder.amount_msats(payment_size_msat.unwrap());
1677+
offer_builder.build().unwrap()
1678+
};
1679+
#[cfg(not(c_bindings))]
1680+
let offer = client_node
1681+
.node
1682+
.create_offer_builder()
1683+
.unwrap()
1684+
.amount_msats(payment_size_msat.unwrap())
1685+
.build()
1686+
.unwrap();
1687+
1688+
let lsps2_router = Arc::new(LSPS2BOLT12Router::new(
1689+
FailingRouter::new(),
1690+
Arc::new(RandomBytes::new([43; 32])),
1691+
));
1692+
lsps2_router.register_offer(
1693+
offer.id(),
1694+
LSPS2Bolt12InvoiceParameters {
1695+
counterparty_node_id: service_node_id,
1696+
intercept_scid,
1697+
cltv_expiry_delta,
1698+
},
1699+
);
1700+
1701+
let lsps2_router = Arc::clone(&lsps2_router);
1702+
*client_node.router.override_create_blinded_payment_paths.lock().unwrap() =
1703+
Some(Box::new(move |recipient, local_node_receive_key, first_hops, tlvs, amount_msats| {
1704+
let secp_ctx = Secp256k1::new();
1705+
lsps2_router.create_blinded_payment_paths(
1706+
recipient,
1707+
local_node_receive_key,
1708+
first_hops,
1709+
tlvs,
1710+
amount_msats,
1711+
&secp_ctx,
1712+
)
1713+
}));
1714+
1715+
let payment_id = PaymentId([1; 32]);
1716+
payer_node.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap();
1717+
1718+
let onion_msg = payer_node
1719+
.onion_messenger
1720+
.next_onion_message_for_peer(service_node_id)
1721+
.expect("Payer should send InvoiceRequest toward service");
1722+
service_node.onion_messenger.handle_onion_message(payer_node_id, &onion_msg);
1723+
1724+
let fwd_msg = service_node
1725+
.onion_messenger
1726+
.next_onion_message_for_peer(client_node_id)
1727+
.expect("Service should forward InvoiceRequest to client");
1728+
client_node.onion_messenger.handle_onion_message(service_node_id, &fwd_msg);
1729+
1730+
let onion_msg = client_node
1731+
.onion_messenger
1732+
.next_onion_message_for_peer(service_node_id)
1733+
.expect("Client should send Invoice toward service");
1734+
service_node.onion_messenger.handle_onion_message(client_node_id, &onion_msg);
1735+
1736+
let fwd_msg = service_node
1737+
.onion_messenger
1738+
.next_onion_message_for_peer(payer_node_id)
1739+
.expect("Service should forward Invoice to payer");
1740+
payer_node.onion_messenger.handle_onion_message(service_node_id, &fwd_msg);
1741+
1742+
check_added_monitors(&payer_node, 1);
1743+
let events = payer_node.node.get_and_clear_pending_msg_events();
1744+
assert_eq!(events.len(), 1);
1745+
let ev = SendEvent::from_event(events[0].clone());
1746+
1747+
service_node.inner.node.handle_update_add_htlc(payer_node_id, &ev.msgs[0]);
1748+
do_commitment_signed_dance(&service_node.inner, &payer_node, &ev.commitment_msg, false, true);
1749+
service_node.inner.node.process_pending_htlc_forwards();
1750+
1751+
let events = service_node.inner.node.get_and_clear_pending_events();
1752+
assert_eq!(events.len(), 1);
1753+
let (payment_hash, expected_outbound_amount_msat) = match &events[0] {
1754+
Event::HTLCIntercepted {
1755+
intercept_id,
1756+
requested_next_hop_scid,
1757+
payment_hash,
1758+
expected_outbound_amount_msat,
1759+
..
1760+
} => {
1761+
assert_eq!(*requested_next_hop_scid, intercept_scid);
1762+
1763+
service_handler
1764+
.htlc_intercepted(
1765+
*requested_next_hop_scid,
1766+
*intercept_id,
1767+
*expected_outbound_amount_msat,
1768+
*payment_hash,
1769+
)
1770+
.unwrap();
1771+
(*payment_hash, expected_outbound_amount_msat)
1772+
},
1773+
other => panic!("Expected HTLCIntercepted event, got: {:?}", other),
1774+
};
1775+
1776+
let open_channel_event = service_node.liquidity_manager.next_event().unwrap();
1777+
1778+
match open_channel_event {
1779+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel {
1780+
their_network_key,
1781+
amt_to_forward_msat,
1782+
opening_fee_msat,
1783+
user_channel_id: uc_id,
1784+
intercept_scid: iscd,
1785+
}) => {
1786+
assert_eq!(their_network_key, client_node_id);
1787+
assert_eq!(amt_to_forward_msat, payment_size_msat.unwrap() - fee_base_msat);
1788+
assert_eq!(opening_fee_msat, fee_base_msat);
1789+
assert_eq!(uc_id, user_channel_id);
1790+
assert_eq!(iscd, intercept_scid);
1791+
},
1792+
other => panic!("Expected OpenChannel event, got: {:?}", other),
1793+
};
1794+
1795+
let result =
1796+
service_handler.channel_needs_manual_broadcast(user_channel_id, &client_node_id).unwrap();
1797+
assert!(result, "Channel should require manual broadcast");
1798+
1799+
let (channel_id, funding_tx) = create_channel_with_manual_broadcast(
1800+
&service_node_id,
1801+
&client_node_id,
1802+
&service_node,
1803+
&client_node,
1804+
user_channel_id,
1805+
expected_outbound_amount_msat,
1806+
true,
1807+
);
1808+
1809+
service_handler.channel_ready(user_channel_id, &channel_id, &client_node_id).unwrap();
1810+
1811+
service_node.inner.node.process_pending_htlc_forwards();
1812+
1813+
let pay_event = {
1814+
{
1815+
let mut added_monitors =
1816+
service_node.inner.chain_monitor.added_monitors.lock().unwrap();
1817+
assert_eq!(added_monitors.len(), 1);
1818+
added_monitors.clear();
1819+
}
1820+
let mut events = service_node.inner.node.get_and_clear_pending_msg_events();
1821+
assert_eq!(events.len(), 1);
1822+
SendEvent::from_event(events.remove(0))
1823+
};
1824+
1825+
client_node.inner.node.handle_update_add_htlc(service_node_id, &pay_event.msgs[0]);
1826+
do_commitment_signed_dance(
1827+
&client_node.inner,
1828+
&service_node.inner,
1829+
&pay_event.commitment_msg,
1830+
false,
1831+
true,
1832+
);
1833+
client_node.inner.node.process_pending_htlc_forwards();
1834+
1835+
let client_events = client_node.inner.node.get_and_clear_pending_events();
1836+
assert_eq!(client_events.len(), 1);
1837+
let preimage = match &client_events[0] {
1838+
Event::PaymentClaimable { payment_hash: ph, purpose, .. } => {
1839+
assert_eq!(*ph, payment_hash);
1840+
purpose.preimage()
1841+
},
1842+
other => panic!("Expected PaymentClaimable event on client, got: {:?}", other),
1843+
};
1844+
1845+
let broadcasted = service_node.inner.tx_broadcaster.txn_broadcasted.lock().unwrap();
1846+
assert!(broadcasted.is_empty(), "There should be no broadcasted txs yet");
1847+
drop(broadcasted);
1848+
1849+
client_node.inner.node.claim_funds(preimage.unwrap());
1850+
1851+
claim_and_assert_forwarded_only(
1852+
&payer_node,
1853+
&service_node.inner,
1854+
&client_node.inner,
1855+
preimage.unwrap(),
1856+
);
1857+
1858+
let service_events = service_node.node.get_and_clear_pending_events();
1859+
assert_eq!(service_events.len(), 1);
1860+
1861+
let total_fee_msat = match service_events[0].clone() {
1862+
Event::PaymentForwarded {
1863+
prev_node_id,
1864+
next_node_id,
1865+
skimmed_fee_msat,
1866+
total_fee_earned_msat,
1867+
..
1868+
} => {
1869+
assert_eq!(prev_node_id, Some(payer_node_id));
1870+
assert_eq!(next_node_id, Some(client_node_id));
1871+
service_handler.payment_forwarded(channel_id, skimmed_fee_msat.unwrap_or(0)).unwrap();
1872+
Some(total_fee_earned_msat.unwrap() - skimmed_fee_msat.unwrap())
1873+
},
1874+
_ => panic!("Expected PaymentForwarded event, got: {:?}", service_events[0]),
1875+
};
1876+
1877+
let broadcasted = service_node.inner.tx_broadcaster.txn_broadcasted.lock().unwrap();
1878+
assert!(broadcasted.iter().any(|b| b.compute_txid() == funding_tx.compute_txid()));
1879+
1880+
expect_payment_sent(&payer_node, preimage.unwrap(), Some(total_fee_msat), true, true);
1881+
}
1882+
16191883
fn create_channel_with_manual_broadcast(
16201884
service_node_id: &PublicKey, client_node_id: &PublicKey, service_node: &LiquidityNode,
16211885
client_node: &LiquidityNode, user_channel_id: u128, expected_outbound_amount_msat: &u64,

0 commit comments

Comments
 (0)