Skip to content

Commit 2cb0546

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 6f17cec commit 2cb0546

File tree

1 file changed

+258
-1
lines changed

1 file changed

+258
-1
lines changed

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 258 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,262 @@ 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+
let offer = client_node
1674+
.node
1675+
.create_offer_builder()
1676+
.unwrap()
1677+
.amount_msats(payment_size_msat.unwrap())
1678+
.build()
1679+
.unwrap();
1680+
1681+
let lsps2_router = Arc::new(LSPS2BOLT12Router::new(
1682+
FailingRouter::new(),
1683+
Arc::new(RandomBytes::new([43; 32])),
1684+
));
1685+
lsps2_router.register_offer(
1686+
offer.id(),
1687+
LSPS2Bolt12InvoiceParameters {
1688+
counterparty_node_id: service_node_id,
1689+
intercept_scid,
1690+
cltv_expiry_delta,
1691+
},
1692+
);
1693+
1694+
let lsps2_router = Arc::clone(&lsps2_router);
1695+
*client_node.router.override_create_blinded_payment_paths.lock().unwrap() =
1696+
Some(Box::new(move |recipient, local_node_receive_key, first_hops, tlvs, amount_msats| {
1697+
let secp_ctx = Secp256k1::new();
1698+
lsps2_router.create_blinded_payment_paths(
1699+
recipient,
1700+
local_node_receive_key,
1701+
first_hops,
1702+
tlvs,
1703+
amount_msats,
1704+
&secp_ctx,
1705+
)
1706+
}));
1707+
1708+
let payment_id = PaymentId([1; 32]);
1709+
payer_node.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap();
1710+
1711+
let onion_msg = payer_node
1712+
.onion_messenger
1713+
.next_onion_message_for_peer(service_node_id)
1714+
.expect("Payer should send InvoiceRequest toward service");
1715+
service_node.onion_messenger.handle_onion_message(payer_node_id, &onion_msg);
1716+
1717+
let fwd_msg = service_node
1718+
.onion_messenger
1719+
.next_onion_message_for_peer(client_node_id)
1720+
.expect("Service should forward InvoiceRequest to client");
1721+
client_node.onion_messenger.handle_onion_message(service_node_id, &fwd_msg);
1722+
1723+
let onion_msg = client_node
1724+
.onion_messenger
1725+
.next_onion_message_for_peer(service_node_id)
1726+
.expect("Client should send Invoice toward service");
1727+
service_node.onion_messenger.handle_onion_message(client_node_id, &onion_msg);
1728+
1729+
let fwd_msg = service_node
1730+
.onion_messenger
1731+
.next_onion_message_for_peer(payer_node_id)
1732+
.expect("Service should forward Invoice to payer");
1733+
payer_node.onion_messenger.handle_onion_message(service_node_id, &fwd_msg);
1734+
1735+
check_added_monitors(&payer_node, 1);
1736+
let events = payer_node.node.get_and_clear_pending_msg_events();
1737+
assert_eq!(events.len(), 1);
1738+
let ev = SendEvent::from_event(events[0].clone());
1739+
1740+
service_node.inner.node.handle_update_add_htlc(payer_node_id, &ev.msgs[0]);
1741+
do_commitment_signed_dance(&service_node.inner, &payer_node, &ev.commitment_msg, false, true);
1742+
service_node.inner.node.process_pending_htlc_forwards();
1743+
1744+
let events = service_node.inner.node.get_and_clear_pending_events();
1745+
assert_eq!(events.len(), 1);
1746+
let (payment_hash, expected_outbound_amount_msat) = match &events[0] {
1747+
Event::HTLCIntercepted {
1748+
intercept_id,
1749+
requested_next_hop_scid,
1750+
payment_hash,
1751+
expected_outbound_amount_msat,
1752+
..
1753+
} => {
1754+
assert_eq!(*requested_next_hop_scid, intercept_scid);
1755+
1756+
service_handler
1757+
.htlc_intercepted(
1758+
*requested_next_hop_scid,
1759+
*intercept_id,
1760+
*expected_outbound_amount_msat,
1761+
*payment_hash,
1762+
)
1763+
.unwrap();
1764+
(*payment_hash, expected_outbound_amount_msat)
1765+
},
1766+
other => panic!("Expected HTLCIntercepted event, got: {:?}", other),
1767+
};
1768+
1769+
let open_channel_event = service_node.liquidity_manager.next_event().unwrap();
1770+
1771+
match open_channel_event {
1772+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel {
1773+
their_network_key,
1774+
amt_to_forward_msat,
1775+
opening_fee_msat,
1776+
user_channel_id: uc_id,
1777+
intercept_scid: iscd,
1778+
}) => {
1779+
assert_eq!(their_network_key, client_node_id);
1780+
assert_eq!(amt_to_forward_msat, payment_size_msat.unwrap() - fee_base_msat);
1781+
assert_eq!(opening_fee_msat, fee_base_msat);
1782+
assert_eq!(uc_id, user_channel_id);
1783+
assert_eq!(iscd, intercept_scid);
1784+
},
1785+
other => panic!("Expected OpenChannel event, got: {:?}", other),
1786+
};
1787+
1788+
let result =
1789+
service_handler.channel_needs_manual_broadcast(user_channel_id, &client_node_id).unwrap();
1790+
assert!(result, "Channel should require manual broadcast");
1791+
1792+
let (channel_id, funding_tx) = create_channel_with_manual_broadcast(
1793+
&service_node_id,
1794+
&client_node_id,
1795+
&service_node,
1796+
&client_node,
1797+
user_channel_id,
1798+
expected_outbound_amount_msat,
1799+
true,
1800+
);
1801+
1802+
service_handler.channel_ready(user_channel_id, &channel_id, &client_node_id).unwrap();
1803+
1804+
service_node.inner.node.process_pending_htlc_forwards();
1805+
1806+
let pay_event = {
1807+
{
1808+
let mut added_monitors =
1809+
service_node.inner.chain_monitor.added_monitors.lock().unwrap();
1810+
assert_eq!(added_monitors.len(), 1);
1811+
added_monitors.clear();
1812+
}
1813+
let mut events = service_node.inner.node.get_and_clear_pending_msg_events();
1814+
assert_eq!(events.len(), 1);
1815+
SendEvent::from_event(events.remove(0))
1816+
};
1817+
1818+
client_node.inner.node.handle_update_add_htlc(service_node_id, &pay_event.msgs[0]);
1819+
do_commitment_signed_dance(
1820+
&client_node.inner,
1821+
&service_node.inner,
1822+
&pay_event.commitment_msg,
1823+
false,
1824+
true,
1825+
);
1826+
client_node.inner.node.process_pending_htlc_forwards();
1827+
1828+
let client_events = client_node.inner.node.get_and_clear_pending_events();
1829+
assert_eq!(client_events.len(), 1);
1830+
let preimage = match &client_events[0] {
1831+
Event::PaymentClaimable { payment_hash: ph, purpose, .. } => {
1832+
assert_eq!(*ph, payment_hash);
1833+
purpose.preimage()
1834+
},
1835+
other => panic!("Expected PaymentClaimable event on client, got: {:?}", other),
1836+
};
1837+
1838+
let broadcasted = service_node.inner.tx_broadcaster.txn_broadcasted.lock().unwrap();
1839+
assert!(broadcasted.is_empty(), "There should be no broadcasted txs yet");
1840+
drop(broadcasted);
1841+
1842+
client_node.inner.node.claim_funds(preimage.unwrap());
1843+
1844+
claim_and_assert_forwarded_only(
1845+
&payer_node,
1846+
&service_node.inner,
1847+
&client_node.inner,
1848+
preimage.unwrap(),
1849+
);
1850+
1851+
let service_events = service_node.node.get_and_clear_pending_events();
1852+
assert_eq!(service_events.len(), 1);
1853+
1854+
let total_fee_msat = match service_events[0].clone() {
1855+
Event::PaymentForwarded {
1856+
prev_node_id,
1857+
next_node_id,
1858+
skimmed_fee_msat,
1859+
total_fee_earned_msat,
1860+
..
1861+
} => {
1862+
assert_eq!(prev_node_id, Some(payer_node_id));
1863+
assert_eq!(next_node_id, Some(client_node_id));
1864+
service_handler.payment_forwarded(channel_id, skimmed_fee_msat.unwrap_or(0)).unwrap();
1865+
Some(total_fee_earned_msat.unwrap() - skimmed_fee_msat.unwrap())
1866+
},
1867+
_ => panic!("Expected PaymentForwarded event, got: {:?}", service_events[0]),
1868+
};
1869+
1870+
let broadcasted = service_node.inner.tx_broadcaster.txn_broadcasted.lock().unwrap();
1871+
assert!(broadcasted.iter().any(|b| b.compute_txid() == funding_tx.compute_txid()));
1872+
1873+
expect_payment_sent(&payer_node, preimage.unwrap(), Some(total_fee_msat), true, true);
1874+
}
1875+
16191876
fn create_channel_with_manual_broadcast(
16201877
service_node_id: &PublicKey, client_node_id: &PublicKey, service_node: &LiquidityNode,
16211878
client_node: &LiquidityNode, user_channel_id: u128, expected_outbound_amount_msat: &u64,

0 commit comments

Comments
 (0)