22
33mod common;
44
5- use common:: { create_service_and_client_nodes, get_lsps_message, LSPSNodes , LiquidityNode } ;
5+ use common:: {
6+ create_service_and_client_nodes, create_service_and_client_nodes_with_kv_stores,
7+ get_lsps_message, LSPSNodes , LiquidityNode ,
8+ } ;
69
10+ use lightning:: chain:: { BestBlock , Filter } ;
711use lightning:: check_closed_event;
812use lightning:: events:: ClosureReason ;
9- use lightning:: ln:: channelmanager:: InterceptId ;
13+ use lightning:: ln:: channelmanager:: { ChainParameters , InterceptId } ;
1014use lightning:: ln:: functional_test_utils:: {
1115 close_channel, create_chan_between_nodes, create_chanmon_cfgs, create_network,
1216 create_node_cfgs, create_node_chanmgrs, Node ,
1317} ;
1418use lightning:: ln:: msgs:: Init ;
1519use lightning:: ln:: peer_handler:: CustomMessageHandler ;
1620use lightning:: util:: hash_tables:: { HashMap , HashSet } ;
21+ use lightning:: util:: test_utils:: TestStore ;
1722use lightning_liquidity:: events:: LiquidityEvent ;
1823use lightning_liquidity:: lsps0:: ser:: LSPSDateTime ;
1924use lightning_liquidity:: lsps2:: client:: LSPS2ClientConfig ;
@@ -34,16 +39,20 @@ use lightning_liquidity::lsps5::service::{
3439} ;
3540use lightning_liquidity:: lsps5:: validator:: { LSPS5Validator , MAX_RECENT_SIGNATURES } ;
3641use lightning_liquidity:: utils:: time:: { DefaultTimeProvider , TimeProvider } ;
42+ use lightning_liquidity:: LiquidityManagerSync ;
3743use lightning_liquidity:: { LiquidityClientConfig , LiquidityServiceConfig } ;
3844
3945use lightning_types:: payment:: PaymentHash ;
4046
47+ use bitcoin:: Network ;
48+
4149use std:: str:: FromStr ;
4250use std:: sync:: { Arc , RwLock } ;
4351use std:: time:: Duration ;
4452
45- pub ( crate ) fn lsps5_test_setup < ' a , ' b , ' c > (
53+ pub ( crate ) fn lsps5_test_setup_with_kv_stores < ' a , ' b , ' c > (
4654 nodes : Vec < Node < ' a , ' b , ' c > > , time_provider : Arc < dyn TimeProvider + Send + Sync > ,
55+ service_kv_store : Arc < TestStore > , client_kv_store : Arc < TestStore > ,
4756) -> ( LSPSNodes < ' a , ' b , ' c > , LSPS5Validator ) {
4857 let lsps5_service_config = LSPS5ServiceConfig :: default ( ) ;
4958 let service_config = LiquidityServiceConfig {
@@ -62,18 +71,28 @@ pub(crate) fn lsps5_test_setup<'a, 'b, 'c>(
6271 lsps5_client_config : Some ( lsps5_client_config) ,
6372 } ;
6473
65- let lsps_nodes = create_service_and_client_nodes (
74+ let lsps_nodes = create_service_and_client_nodes_with_kv_stores (
6675 nodes,
6776 service_config,
6877 client_config,
6978 Arc :: clone ( & time_provider) ,
79+ service_kv_store,
80+ client_kv_store,
7081 ) ;
7182
7283 let validator = LSPS5Validator :: new ( ) ;
7384
7485 ( lsps_nodes, validator)
7586}
7687
88+ pub ( crate ) fn lsps5_test_setup < ' a , ' b , ' c > (
89+ nodes : Vec < Node < ' a , ' b , ' c > > , time_provider : Arc < dyn TimeProvider + Send + Sync > ,
90+ ) -> ( LSPSNodes < ' a , ' b , ' c > , LSPS5Validator ) {
91+ let service_kv_store = Arc :: new ( TestStore :: new ( false ) ) ;
92+ let client_kv_store = Arc :: new ( TestStore :: new ( false ) ) ;
93+ lsps5_test_setup_with_kv_stores ( nodes, time_provider, service_kv_store, client_kv_store)
94+ }
95+
7796fn assert_lsps5_reject (
7897 service_node : & LiquidityNode < ' _ , ' _ , ' _ > , client_node : & LiquidityNode < ' _ , ' _ , ' _ > ,
7998) {
@@ -1479,3 +1498,147 @@ fn lsps2_state_allows_lsps5_request() {
14791498
14801499 assert_lsps5_accept ( & lsps_nodes. service_node , & lsps_nodes. client_node ) ;
14811500}
1501+
1502+ #[ test]
1503+ fn lsps5_service_handler_persistence_across_restarts ( ) {
1504+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
1505+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
1506+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
1507+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
1508+
1509+ // Create shared KV store for service node that will persist across restarts
1510+ let service_kv_store = Arc :: new ( TestStore :: new ( false ) ) ;
1511+ let client_kv_store = Arc :: new ( TestStore :: new ( false ) ) ;
1512+
1513+ let service_config = LiquidityServiceConfig {
1514+ #[ cfg( lsps1_service) ]
1515+ lsps1_service_config : None ,
1516+ lsps2_service_config : None ,
1517+ lsps5_service_config : Some ( LSPS5ServiceConfig :: default ( ) ) ,
1518+ advertise_service : true ,
1519+ } ;
1520+ let time_provider: Arc < dyn TimeProvider + Send + Sync > = Arc :: new ( DefaultTimeProvider ) ;
1521+
1522+ // Variables to carry state between scopes
1523+ let client_node_id;
1524+ let app_name = "PersistenceTestApp" ;
1525+ let webhook_url = "https://example.org/persistence-test" ;
1526+
1527+ // First scope: Setup, webhook registration, persistence, and dropping of all node objects
1528+ {
1529+ // Use the helper function with custom KV stores
1530+ let ( lsps_nodes, _validator) = lsps5_test_setup_with_kv_stores (
1531+ nodes,
1532+ Arc :: clone ( & time_provider) ,
1533+ Arc :: clone ( & service_kv_store) ,
1534+ client_kv_store,
1535+ ) ;
1536+ let LSPSNodes { service_node, client_node } = lsps_nodes;
1537+
1538+ // Establish a channel to meet LSPS5 requirements
1539+ create_chan_between_nodes ( & service_node. inner , & client_node. inner ) ;
1540+
1541+ let service_node_id = service_node. inner . node . get_our_node_id ( ) ;
1542+ client_node_id = client_node. inner . node . get_our_node_id ( ) ;
1543+
1544+ let client_handler = client_node. liquidity_manager . lsps5_client_handler ( ) . unwrap ( ) ;
1545+
1546+ // Register a webhook to create state that needs persistence
1547+ let _request_id = client_handler
1548+ . set_webhook ( service_node_id, app_name. to_string ( ) , webhook_url. to_string ( ) )
1549+ . expect ( "Register webhook request should succeed" ) ;
1550+ let set_webhook_request = get_lsps_message ! ( client_node, service_node_id) ;
1551+
1552+ service_node
1553+ . liquidity_manager
1554+ . handle_custom_message ( set_webhook_request, client_node_id)
1555+ . unwrap ( ) ;
1556+
1557+ // Consume SendWebhookNotification event for webhook_registered
1558+ let webhook_notification_event = service_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
1559+ match webhook_notification_event {
1560+ LiquidityEvent :: LSPS5Service ( LSPS5ServiceEvent :: SendWebhookNotification {
1561+ counterparty_node_id,
1562+ notification,
1563+ ..
1564+ } ) => {
1565+ assert_eq ! ( counterparty_node_id, client_node_id) ;
1566+ assert_eq ! ( notification. method, WebhookNotificationMethod :: LSPS5WebhookRegistered ) ;
1567+ } ,
1568+ _ => panic ! ( "Expected SendWebhookNotification event" ) ,
1569+ }
1570+
1571+ let set_webhook_response = get_lsps_message ! ( service_node, client_node_id) ;
1572+ client_node
1573+ . liquidity_manager
1574+ . handle_custom_message ( set_webhook_response, service_node_id)
1575+ . unwrap ( ) ;
1576+
1577+ let webhook_registered_event = client_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
1578+ match webhook_registered_event {
1579+ LiquidityEvent :: LSPS5Client ( LSPS5ClientEvent :: WebhookRegistered {
1580+ num_webhooks,
1581+ ..
1582+ } ) => {
1583+ assert_eq ! ( num_webhooks, 1 ) ;
1584+ } ,
1585+ _ => panic ! ( "Expected WebhookRegistered event" ) ,
1586+ }
1587+
1588+ // Trigger persistence by calling persist
1589+ service_node. liquidity_manager . persist ( ) . unwrap ( ) ;
1590+
1591+ // All node objects are dropped at the end of this scope
1592+ }
1593+
1594+ // Second scope: Recovery from persisted store and verification
1595+ {
1596+ // Create fresh node configurations for restart to avoid connection conflicts
1597+ let node_chanmgrs_restart = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
1598+ let nodes_restart = create_network ( 2 , & node_cfgs, & node_chanmgrs_restart) ;
1599+
1600+ // Create a new LiquidityManager with the same configuration and KV store to simulate restart
1601+ let chain_params = ChainParameters {
1602+ network : Network :: Testnet ,
1603+ best_block : BestBlock :: from_network ( Network :: Testnet ) ,
1604+ } ;
1605+
1606+ let restarted_service_lm = LiquidityManagerSync :: new_with_custom_time_provider (
1607+ nodes_restart[ 0 ] . keys_manager ,
1608+ nodes_restart[ 0 ] . keys_manager ,
1609+ nodes_restart[ 0 ] . node ,
1610+ None :: < Arc < dyn Filter + Send + Sync > > ,
1611+ Some ( chain_params) ,
1612+ service_kv_store,
1613+ Some ( service_config) ,
1614+ None ,
1615+ Arc :: clone ( & time_provider) ,
1616+ )
1617+ . unwrap ( ) ;
1618+
1619+ let restarted_service_handler = restarted_service_lm. lsps5_service_handler ( ) . unwrap ( ) ;
1620+
1621+ // Verify the state was properly restored by attempting to send a notification
1622+ // This should succeed if the webhook state was properly restored
1623+ let result = restarted_service_handler. notify_payment_incoming ( client_node_id) ;
1624+ assert ! ( result. is_ok( ) , "Notification should succeed with restored webhook state" ) ;
1625+
1626+ // Check that we get a SendWebhookNotification event, confirming the state was restored correctly
1627+ let event = restarted_service_lm. next_event ( ) ;
1628+ assert ! ( event. is_some( ) , "Should have an event after sending notification" ) ;
1629+
1630+ if let Some ( LiquidityEvent :: LSPS5Service ( LSPS5ServiceEvent :: SendWebhookNotification {
1631+ counterparty_node_id,
1632+ url,
1633+ notification,
1634+ ..
1635+ } ) ) = event
1636+ {
1637+ assert_eq ! ( counterparty_node_id, client_node_id) ;
1638+ assert_eq ! ( url. as_str( ) , webhook_url) ;
1639+ assert_eq ! ( notification. method, WebhookNotificationMethod :: LSPS5PaymentIncoming ) ;
1640+ } else {
1641+ panic ! ( "Expected SendWebhookNotification event after restart" ) ;
1642+ }
1643+ }
1644+ }
0 commit comments