@@ -15,16 +15,22 @@ use lightning_liquidity::lsps1::msgs::{
1515} ;
1616use lightning_liquidity:: lsps1:: service:: LSPS1ServiceConfig ;
1717use lightning_liquidity:: utils:: time:: DefaultTimeProvider ;
18- use lightning_liquidity:: { LiquidityClientConfig , LiquidityServiceConfig } ;
18+ use lightning_liquidity:: { LiquidityClientConfig , LiquidityManagerSync , LiquidityServiceConfig } ;
1919
2020use lightning:: ln:: functional_test_utils:: {
2121 create_chanmon_cfgs, create_node_cfgs, create_node_chanmgrs,
2222} ;
23- use lightning:: util:: test_utils:: TestStore ;
23+ use lightning:: util:: test_utils:: { TestBroadcaster , TestStore } ;
2424
25+ use bitcoin:: secp256k1:: PublicKey ;
26+ use bitcoin:: { Address , Network } ;
27+
28+ use std:: str:: FromStr ;
2529use std:: sync:: Arc ;
2630
2731use lightning:: ln:: functional_test_utils:: { create_network, Node } ;
32+ use lightning_liquidity:: lsps1:: msgs:: LSPS1OrderId ;
33+ use lightning_liquidity:: utils:: time:: TimeProvider ;
2834
2935fn build_lsps1_configs (
3036 supported_options : LSPS1Options ,
@@ -240,3 +246,252 @@ fn lsps1_happy_path() {
240246 panic ! ( "Unexpected event" ) ;
241247 }
242248}
249+
250+ #[ test]
251+ fn lsps1_service_handler_persistence_across_restarts ( ) {
252+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
253+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
254+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
255+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
256+
257+ // Create shared KV store for service node that will persist across restarts
258+ let service_kv_store = Arc :: new ( TestStore :: new ( false ) ) ;
259+ let client_kv_store = Arc :: new ( TestStore :: new ( false ) ) ;
260+
261+ let supported_options = LSPS1Options {
262+ min_required_channel_confirmations : 0 ,
263+ min_funding_confirms_within_blocks : 6 ,
264+ supports_zero_channel_reserve : true ,
265+ max_channel_expiry_blocks : 144 ,
266+ min_initial_client_balance_sat : 10_000_000 ,
267+ max_initial_client_balance_sat : 100_000_000 ,
268+ min_initial_lsp_balance_sat : 100_000 ,
269+ max_initial_lsp_balance_sat : 100_000_000 ,
270+ min_channel_balance_sat : 100_000 ,
271+ max_channel_balance_sat : 100_000_000 ,
272+ } ;
273+
274+ let service_config = LiquidityServiceConfig {
275+ lsps1_service_config : Some ( LSPS1ServiceConfig {
276+ supported_options : supported_options. clone ( ) ,
277+ token : None ,
278+ } ) ,
279+ lsps2_service_config : None ,
280+ lsps5_service_config : None ,
281+ advertise_service : true ,
282+ } ;
283+ let time_provider: Arc < dyn TimeProvider + Send + Sync > = Arc :: new ( DefaultTimeProvider ) ;
284+
285+ // Variables to carry state between scopes
286+ let client_node_id: PublicKey ;
287+ let expected_order_id: LSPS1OrderId ;
288+ let order_params: LSPS1OrderParams ;
289+ let payment_info: LSPS1PaymentInfo ;
290+
291+ // First scope: Setup, persistence, and dropping of all node objects
292+ {
293+ let LSPSNodes { service_node, client_node } = setup_test_lsps1_nodes_with_kv_stores (
294+ nodes,
295+ Arc :: clone ( & service_kv_store) ,
296+ client_kv_store,
297+ supported_options. clone ( ) ,
298+ ) ;
299+
300+ let service_node_id = service_node. inner . node . get_our_node_id ( ) ;
301+ client_node_id = client_node. inner . node . get_our_node_id ( ) ;
302+
303+ let client_handler = client_node. liquidity_manager . lsps1_client_handler ( ) . unwrap ( ) ;
304+ let service_handler = service_node. liquidity_manager . lsps1_service_handler ( ) . unwrap ( ) ;
305+
306+ // Request supported options
307+ let _request_supported_options_id =
308+ client_handler. request_supported_options ( service_node_id) ;
309+ let request_supported_options = get_lsps_message ! ( client_node, service_node_id) ;
310+
311+ service_node
312+ . liquidity_manager
313+ . handle_custom_message ( request_supported_options, client_node_id)
314+ . unwrap ( ) ;
315+
316+ let get_info_message = get_lsps_message ! ( service_node, client_node_id) ;
317+ client_node
318+ . liquidity_manager
319+ . handle_custom_message ( get_info_message, service_node_id)
320+ . unwrap ( ) ;
321+
322+ let _get_info_event = client_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
323+
324+ // Create an order to establish persistent state
325+ order_params = LSPS1OrderParams {
326+ lsp_balance_sat : 100_000 ,
327+ client_balance_sat : 10_000_000 ,
328+ required_channel_confirmations : 0 ,
329+ funding_confirms_within_blocks : 6 ,
330+ channel_expiry_blocks : 144 ,
331+ token : None ,
332+ announce_channel : true ,
333+ } ;
334+
335+ let refund_onchain_address =
336+ Address :: from_str ( "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr" )
337+ . unwrap ( )
338+ . assume_checked ( ) ;
339+ let create_order_id = client_handler. create_order (
340+ & service_node_id,
341+ order_params. clone ( ) ,
342+ Some ( refund_onchain_address) ,
343+ ) ;
344+ let create_order = get_lsps_message ! ( client_node, service_node_id) ;
345+
346+ service_node. liquidity_manager . handle_custom_message ( create_order, client_node_id) . unwrap ( ) ;
347+
348+ let request_for_payment_event = service_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
349+ let request_id =
350+ if let LiquidityEvent :: LSPS1Service ( LSPS1ServiceEvent :: RequestForPaymentDetails {
351+ request_id,
352+ ..
353+ } ) = request_for_payment_event
354+ {
355+ request_id
356+ } else {
357+ panic ! ( "Unexpected event" ) ;
358+ } ;
359+
360+ // Service sends payment details, creating persistent order state
361+ let json_str = r#"{
362+ "state": "EXPECT_PAYMENT",
363+ "expires_at": "2035-01-01T00:00:00Z",
364+ "fee_total_sat": "9999",
365+ "order_total_sat": "200999",
366+ "address": "bc1p5uvtaxzkjwvey2tfy49k5vtqfpjmrgm09cvs88ezyy8h2zv7jhas9tu4yr",
367+ "min_onchain_payment_confirmations": 1,
368+ "min_fee_for_0conf": 253
369+ }"# ;
370+
371+ let onchain: LSPS1OnchainPaymentInfo =
372+ serde_json:: from_str ( json_str) . expect ( "Failed to parse JSON" ) ;
373+ payment_info = LSPS1PaymentInfo { bolt11 : None , bolt12 : None , onchain : Some ( onchain) } ;
374+ service_handler
375+ . send_payment_details ( request_id. clone ( ) , client_node_id, payment_info. clone ( ) )
376+ . unwrap ( ) ;
377+
378+ let create_order_response = get_lsps_message ! ( service_node, client_node_id) ;
379+
380+ client_node
381+ . liquidity_manager
382+ . handle_custom_message ( create_order_response, service_node_id)
383+ . unwrap ( ) ;
384+
385+ let order_created_event = client_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
386+ expected_order_id = if let LiquidityEvent :: LSPS1Client ( LSPS1ClientEvent :: OrderCreated {
387+ request_id,
388+ order_id,
389+ ..
390+ } ) = order_created_event
391+ {
392+ assert_eq ! ( request_id, create_order_id) ;
393+ order_id
394+ } else {
395+ panic ! ( "Unexpected event" ) ;
396+ } ;
397+
398+ // Trigger persistence by calling persist
399+ service_node. liquidity_manager . persist ( ) . unwrap ( ) ;
400+
401+ // All node objects are dropped at the end of this scope
402+ }
403+
404+ // Second scope: Recovery from persisted store and verification
405+ {
406+ // Create fresh node configurations for restart
407+ let node_chanmgrs_restart = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , None ] ) ;
408+ let nodes_restart = create_network ( 2 , & node_cfgs, & node_chanmgrs_restart) ;
409+
410+ // Create a new LiquidityManager with the same configuration and KV store to simulate restart
411+ let service_transaction_broadcaster = Arc :: new ( TestBroadcaster :: new ( Network :: Testnet ) ) ;
412+ let client_transaction_broadcaster = Arc :: new ( TestBroadcaster :: new ( Network :: Testnet ) ) ;
413+ let client_kv_store_restart = Arc :: new ( TestStore :: new ( false ) ) ;
414+
415+ let restarted_service_lm = LiquidityManagerSync :: new_with_custom_time_provider (
416+ nodes_restart[ 0 ] . keys_manager ,
417+ nodes_restart[ 0 ] . keys_manager ,
418+ nodes_restart[ 0 ] . node ,
419+ service_kv_store,
420+ service_transaction_broadcaster,
421+ Some ( service_config) ,
422+ None ,
423+ Arc :: clone ( & time_provider) ,
424+ )
425+ . unwrap ( ) ;
426+
427+ // Create a fresh client to query the restarted service
428+ let lsps1_client_config = LSPS1ClientConfig { max_channel_fees_msat : None } ;
429+ let client_config = LiquidityClientConfig {
430+ lsps1_client_config : Some ( lsps1_client_config) ,
431+ lsps2_client_config : None ,
432+ lsps5_client_config : None ,
433+ } ;
434+
435+ let client_lm = LiquidityManagerSync :: new_with_custom_time_provider (
436+ nodes_restart[ 1 ] . keys_manager ,
437+ nodes_restart[ 1 ] . keys_manager ,
438+ nodes_restart[ 1 ] . node ,
439+ client_kv_store_restart,
440+ client_transaction_broadcaster,
441+ None ,
442+ Some ( client_config) ,
443+ time_provider,
444+ )
445+ . unwrap ( ) ;
446+
447+ let service_node_id = nodes_restart[ 0 ] . node . get_our_node_id ( ) ;
448+ let client_node_id_restart = nodes_restart[ 1 ] . node . get_our_node_id ( ) ;
449+
450+ // Verify node IDs match (since we use same node_cfgs)
451+ assert_eq ! ( client_node_id_restart, client_node_id) ;
452+
453+ // Use the client to send a GetOrder request
454+ let client_handler = client_lm. lsps1_client_handler ( ) . unwrap ( ) ;
455+ let check_order_status_id =
456+ client_handler. check_order_status ( & service_node_id, expected_order_id. clone ( ) ) ;
457+
458+ // Get the request message from client
459+ let pending_client_msgs = client_lm. get_and_clear_pending_msg ( ) ;
460+ assert_eq ! ( pending_client_msgs. len( ) , 1 ) ;
461+ let ( target_node_id, request_msg) = pending_client_msgs. into_iter ( ) . next ( ) . unwrap ( ) ;
462+ assert_eq ! ( target_node_id, service_node_id) ;
463+
464+ // Pass the request to the restarted service
465+ restarted_service_lm. handle_custom_message ( request_msg, client_node_id) . unwrap ( ) ;
466+
467+ // Get the response from the service
468+ let pending_service_msgs = restarted_service_lm. get_and_clear_pending_msg ( ) ;
469+ assert_eq ! ( pending_service_msgs. len( ) , 1 ) ;
470+ let ( target_node_id, response_msg) = pending_service_msgs. into_iter ( ) . next ( ) . unwrap ( ) ;
471+ assert_eq ! ( target_node_id, client_node_id) ;
472+
473+ // Pass the response to the client
474+ client_lm. handle_custom_message ( response_msg, service_node_id) . unwrap ( ) ;
475+
476+ // Verify the client receives the order status event with correct data
477+ let order_status_event = client_lm. next_event ( ) . unwrap ( ) ;
478+ if let LiquidityEvent :: LSPS1Client ( LSPS1ClientEvent :: OrderStatus {
479+ request_id,
480+ counterparty_node_id,
481+ order_id,
482+ order,
483+ payment,
484+ channel,
485+ } ) = order_status_event
486+ {
487+ assert_eq ! ( request_id, check_order_status_id) ;
488+ assert_eq ! ( counterparty_node_id, service_node_id) ;
489+ assert_eq ! ( order_id, expected_order_id) ;
490+ assert_eq ! ( order, order_params) ;
491+ assert_eq ! ( payment, payment_info) ;
492+ assert ! ( channel. is_none( ) ) ;
493+ } else {
494+ panic ! ( "Expected OrderStatus event after restart, got: {:?}" , order_status_event) ;
495+ }
496+ }
497+ }
0 commit comments