2
2
3
3
mod common;
4
4
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
+ } ;
6
9
10
+ use lightning:: chain:: { BestBlock , Filter } ;
7
11
use lightning:: check_closed_event;
8
12
use lightning:: events:: ClosureReason ;
9
- use lightning:: ln:: channelmanager:: InterceptId ;
13
+ use lightning:: ln:: channelmanager:: { ChainParameters , InterceptId } ;
10
14
use lightning:: ln:: functional_test_utils:: {
11
15
close_channel, create_chan_between_nodes, create_chanmon_cfgs, create_network,
12
16
create_node_cfgs, create_node_chanmgrs, Node ,
13
17
} ;
14
18
use lightning:: ln:: msgs:: Init ;
15
19
use lightning:: ln:: peer_handler:: CustomMessageHandler ;
16
20
use lightning:: util:: hash_tables:: { HashMap , HashSet } ;
21
+ use lightning:: util:: test_utils:: TestStore ;
17
22
use lightning_liquidity:: events:: LiquidityEvent ;
18
23
use lightning_liquidity:: lsps0:: ser:: LSPSDateTime ;
19
24
use lightning_liquidity:: lsps2:: client:: LSPS2ClientConfig ;
@@ -34,16 +39,20 @@ use lightning_liquidity::lsps5::service::{
34
39
} ;
35
40
use lightning_liquidity:: lsps5:: validator:: { LSPS5Validator , MAX_RECENT_SIGNATURES } ;
36
41
use lightning_liquidity:: utils:: time:: { DefaultTimeProvider , TimeProvider } ;
42
+ use lightning_liquidity:: LiquidityManagerSync ;
37
43
use lightning_liquidity:: { LiquidityClientConfig , LiquidityServiceConfig } ;
38
44
39
45
use lightning_types:: payment:: PaymentHash ;
40
46
47
+ use bitcoin:: Network ;
48
+
41
49
use std:: str:: FromStr ;
42
50
use std:: sync:: { Arc , RwLock } ;
43
51
use std:: time:: Duration ;
44
52
45
- pub ( crate ) fn lsps5_test_setup < ' a , ' b , ' c > (
53
+ pub ( crate ) fn lsps5_test_setup_with_kv_stores < ' a , ' b , ' c > (
46
54
nodes : Vec < Node < ' a , ' b , ' c > > , time_provider : Arc < dyn TimeProvider + Send + Sync > ,
55
+ service_kv_store : Arc < TestStore > , client_kv_store : Arc < TestStore > ,
47
56
) -> ( LSPSNodes < ' a , ' b , ' c > , LSPS5Validator ) {
48
57
let lsps5_service_config = LSPS5ServiceConfig :: default ( ) ;
49
58
let service_config = LiquidityServiceConfig {
@@ -62,18 +71,28 @@ pub(crate) fn lsps5_test_setup<'a, 'b, 'c>(
62
71
lsps5_client_config : Some ( lsps5_client_config) ,
63
72
} ;
64
73
65
- let lsps_nodes = create_service_and_client_nodes (
74
+ let lsps_nodes = create_service_and_client_nodes_with_kv_stores (
66
75
nodes,
67
76
service_config,
68
77
client_config,
69
78
Arc :: clone ( & time_provider) ,
79
+ service_kv_store,
80
+ client_kv_store,
70
81
) ;
71
82
72
83
let validator = LSPS5Validator :: new ( ) ;
73
84
74
85
( lsps_nodes, validator)
75
86
}
76
87
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
+
77
96
fn assert_lsps5_reject (
78
97
service_node : & LiquidityNode < ' _ , ' _ , ' _ > , client_node : & LiquidityNode < ' _ , ' _ , ' _ > ,
79
98
) {
@@ -1479,3 +1498,147 @@ fn lsps2_state_allows_lsps5_request() {
1479
1498
1480
1499
assert_lsps5_accept ( & lsps_nodes. service_node , & lsps_nodes. client_node ) ;
1481
1500
}
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