@@ -11,23 +11,54 @@ use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent};
1111use lightning_liquidity:: lsps2:: msgs:: LSPS2RawOpeningFeeParams ;
1212use lightning_liquidity:: lsps2:: service:: LSPS2ServiceConfig ;
1313use lightning_liquidity:: lsps2:: utils:: is_valid_opening_fee_params;
14- use lightning_liquidity:: { LiquidityClientConfig , LiquidityServiceConfig } ;
1514
16- use lightning:: ln:: channelmanager:: MIN_FINAL_CLTV_EXPIRY_DELTA ;
15+ use lightning:: ln:: channelmanager:: { InterceptId , MIN_FINAL_CLTV_EXPIRY_DELTA } ;
1716use lightning:: ln:: peer_handler:: CustomMessageHandler ;
1817use lightning:: log_error;
1918use lightning:: routing:: router:: { RouteHint , RouteHintHop } ;
19+ use lightning:: util:: errors:: APIError ;
2020use lightning:: util:: logger:: Logger ;
2121
2222use lightning_invoice:: { Bolt11Invoice , InvoiceBuilder , RoutingFees } ;
2323
24+ use lightning_liquidity:: { LiquidityClientConfig , LiquidityServiceConfig } ;
25+ use lightning_types:: payment:: PaymentHash ;
26+
2427use bitcoin:: hashes:: { sha256, Hash } ;
25- use bitcoin:: secp256k1:: { PublicKey , Secp256k1 } ;
28+ use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
2629use bitcoin:: Network ;
2730
2831use std:: str:: FromStr ;
2932use std:: time:: Duration ;
3033
34+ fn setup_test_lsps2 (
35+ ) -> ( bitcoin:: secp256k1:: PublicKey , bitcoin:: secp256k1:: PublicKey , Node , Node , [ u8 ; 32 ] ) {
36+ let promise_secret = [ 42 ; 32 ] ;
37+ let signing_key = SecretKey :: from_slice ( & promise_secret) . unwrap ( ) ;
38+ let lsps2_service_config = LSPS2ServiceConfig { promise_secret } ;
39+ let service_config = LiquidityServiceConfig {
40+ #[ cfg( lsps1_service) ]
41+ lsps1_service_config : None ,
42+ lsps2_service_config : Some ( lsps2_service_config) ,
43+ advertise_service : true ,
44+ } ;
45+
46+ let lsps2_client_config = LSPS2ClientConfig :: default ( ) ;
47+ let client_config = LiquidityClientConfig {
48+ lsps1_client_config : None ,
49+ lsps2_client_config : Some ( lsps2_client_config) ,
50+ } ;
51+
52+ let ( service_node, client_node) =
53+ create_service_and_client_nodes ( "webhook_registration_flow" , service_config, client_config) ;
54+
55+ let secp = bitcoin:: secp256k1:: Secp256k1 :: new ( ) ;
56+ let service_node_id = bitcoin:: secp256k1:: PublicKey :: from_secret_key ( & secp, & signing_key) ;
57+ let client_node_id = client_node. channel_manager . get_our_node_id ( ) ;
58+
59+ ( service_node_id, client_node_id, service_node, client_node, promise_secret)
60+ }
61+
3162fn create_jit_invoice (
3263 node : & Node , service_node_id : PublicKey , intercept_scid : u64 , cltv_expiry_delta : u32 ,
3364 payment_size_msat : Option < u64 > , description : & str , expiry_secs : u32 ,
@@ -82,29 +113,11 @@ fn create_jit_invoice(
82113
83114#[ test]
84115fn invoice_generation_flow ( ) {
85- let promise_secret = [ 42 ; 32 ] ;
86- let lsps2_service_config = LSPS2ServiceConfig { promise_secret } ;
87- let service_config = LiquidityServiceConfig {
88- #[ cfg( lsps1_service) ]
89- lsps1_service_config : None ,
90- lsps2_service_config : Some ( lsps2_service_config) ,
91- advertise_service : true ,
92- } ;
93-
94- let lsps2_client_config = LSPS2ClientConfig :: default ( ) ;
95- let client_config = LiquidityClientConfig {
96- lsps1_client_config : None ,
97- lsps2_client_config : Some ( lsps2_client_config) ,
98- } ;
99-
100- let ( service_node, client_node) =
101- create_service_and_client_nodes ( "invoice_generation_flow" , service_config, client_config) ;
102-
103- let service_handler = service_node. liquidity_manager . lsps2_service_handler ( ) . unwrap ( ) ;
104- let service_node_id = service_node. channel_manager . get_our_node_id ( ) ;
116+ let ( service_node_id, client_node_id, service_node, client_node, promise_secret) =
117+ setup_test_lsps2 ( ) ;
105118
106119 let client_handler = client_node. liquidity_manager . lsps2_client_handler ( ) . unwrap ( ) ;
107- let client_node_id = client_node . channel_manager . get_our_node_id ( ) ;
120+ let service_handler = service_node . liquidity_manager . lsps2_service_handler ( ) . unwrap ( ) ;
108121
109122 let get_info_request_id = client_handler. request_opening_params ( service_node_id, None ) ;
110123 let get_info_request = get_lsps_message ! ( client_node, service_node_id) ;
@@ -239,3 +252,255 @@ fn invoice_generation_flow() {
239252 )
240253 . unwrap ( ) ;
241254}
255+
256+ #[ test]
257+ fn channel_open_failed ( ) {
258+ let ( service_node_id, client_node_id, service_node, client_node, _) = setup_test_lsps2 ( ) ;
259+
260+ let service_handler = service_node. liquidity_manager . lsps2_service_handler ( ) . unwrap ( ) ;
261+
262+ let get_info_request_id = client_node
263+ . liquidity_manager
264+ . lsps2_client_handler ( )
265+ . unwrap ( )
266+ . request_opening_params ( service_node_id, None ) ;
267+ let get_info_request = get_lsps_message ! ( client_node, service_node_id) ;
268+ service_node. liquidity_manager . handle_custom_message ( get_info_request, client_node_id) . unwrap ( ) ;
269+
270+ let _get_info_event = service_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
271+
272+ let raw_opening_params = LSPS2RawOpeningFeeParams {
273+ min_fee_msat : 100 ,
274+ proportional : 21 ,
275+ valid_until : LSPSDateTime :: from_str ( "2035-05-20T08:30:45Z" ) . unwrap ( ) ,
276+ min_lifetime : 144 ,
277+ max_client_to_self_delay : 128 ,
278+ min_payment_size_msat : 1 ,
279+ max_payment_size_msat : 100_000_000 ,
280+ } ;
281+ service_handler
282+ . opening_fee_params_generated (
283+ & client_node_id,
284+ get_info_request_id. clone ( ) ,
285+ vec ! [ raw_opening_params] ,
286+ )
287+ . unwrap ( ) ;
288+
289+ let get_info_response = get_lsps_message ! ( service_node, client_node_id) ;
290+ client_node
291+ . liquidity_manager
292+ . handle_custom_message ( get_info_response, service_node_id)
293+ . unwrap ( ) ;
294+
295+ let opening_fee_params = match client_node. liquidity_manager . next_event ( ) . unwrap ( ) {
296+ LiquidityEvent :: LSPS2Client ( LSPS2ClientEvent :: OpeningParametersReady {
297+ opening_fee_params_menu,
298+ ..
299+ } ) => opening_fee_params_menu. first ( ) . unwrap ( ) . clone ( ) ,
300+ _ => panic ! ( "Unexpected event" ) ,
301+ } ;
302+
303+ let payment_size_msat = Some ( 1_000_000 ) ;
304+ let buy_request_id = client_node
305+ . liquidity_manager
306+ . lsps2_client_handler ( )
307+ . unwrap ( )
308+ . select_opening_params ( service_node_id, payment_size_msat, opening_fee_params. clone ( ) )
309+ . unwrap ( ) ;
310+ let buy_request = get_lsps_message ! ( client_node, service_node_id) ;
311+ service_node. liquidity_manager . handle_custom_message ( buy_request, client_node_id) . unwrap ( ) ;
312+
313+ let _buy_event = service_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
314+ let user_channel_id = 42 ;
315+ let cltv_expiry_delta = 144 ;
316+ let intercept_scid = service_node. channel_manager . get_intercept_scid ( ) ;
317+ let client_trusts_lsp = true ;
318+
319+ service_handler
320+ . invoice_parameters_generated (
321+ & client_node_id,
322+ buy_request_id. clone ( ) ,
323+ intercept_scid,
324+ cltv_expiry_delta,
325+ client_trusts_lsp,
326+ user_channel_id,
327+ )
328+ . unwrap ( ) ;
329+
330+ let buy_response = get_lsps_message ! ( service_node, client_node_id) ;
331+ client_node. liquidity_manager . handle_custom_message ( buy_response, service_node_id) . unwrap ( ) ;
332+ let _invoice_params_event = client_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
333+
334+ // Test calling channel_open_failed in invalid state (before HTLC interception)
335+ let result = service_handler. channel_open_failed ( & client_node_id, user_channel_id) ;
336+ assert ! ( result. is_err( ) ) ;
337+ match result. unwrap_err ( ) {
338+ APIError :: APIMisuseError { err } => {
339+ assert ! ( err. contains( "Channel is not in the PendingChannelOpen state." ) ) ;
340+ } ,
341+ other => panic ! ( "Unexpected error type: {:?}" , other) ,
342+ }
343+
344+ let htlc_amount_msat = 1_000_000 ;
345+ let intercept_id = InterceptId ( [ 0 ; 32 ] ) ;
346+ let payment_hash = PaymentHash ( [ 1 ; 32 ] ) ;
347+
348+ // This should trigger an OpenChannel event
349+ service_handler
350+ . htlc_intercepted ( intercept_scid, intercept_id, htlc_amount_msat, payment_hash)
351+ . unwrap ( ) ;
352+
353+ let _ = match service_node. liquidity_manager . next_event ( ) . unwrap ( ) {
354+ LiquidityEvent :: LSPS2Service ( LSPS2ServiceEvent :: OpenChannel {
355+ user_channel_id : channel_id,
356+ intercept_scid : scid,
357+ ..
358+ } ) => {
359+ assert_eq ! ( channel_id, user_channel_id) ;
360+ assert_eq ! ( scid, intercept_scid) ;
361+ true
362+ } ,
363+ _ => panic ! ( "Expected OpenChannel event" ) ,
364+ } ;
365+
366+ service_handler. channel_open_failed ( & client_node_id, user_channel_id) . unwrap ( ) ;
367+
368+ // Verify we can restart the flow with another HTLC
369+ let new_intercept_id = InterceptId ( [ 1 ; 32 ] ) ;
370+ service_handler
371+ . htlc_intercepted ( intercept_scid, new_intercept_id, htlc_amount_msat, payment_hash)
372+ . unwrap ( ) ;
373+
374+ // Should get another OpenChannel event which confirms the reset worked
375+ let _ = match service_node. liquidity_manager . next_event ( ) . unwrap ( ) {
376+ LiquidityEvent :: LSPS2Service ( LSPS2ServiceEvent :: OpenChannel {
377+ user_channel_id : channel_id,
378+ intercept_scid : scid,
379+ ..
380+ } ) => {
381+ assert_eq ! ( channel_id, user_channel_id) ;
382+ assert_eq ! ( scid, intercept_scid) ;
383+ true
384+ } ,
385+ _ => panic ! ( "Expected OpenChannel event after reset" ) ,
386+ } ;
387+ }
388+
389+ #[ test]
390+ fn channel_open_failed_nonexistent_channel ( ) {
391+ let ( _, client_node_id, service_node, _, _) = setup_test_lsps2 ( ) ;
392+
393+ let service_handler = service_node. liquidity_manager . lsps2_service_handler ( ) . unwrap ( ) ;
394+
395+ // Call channel_open_failed with a nonexistent user_channel_id
396+ let nonexistent_user_channel_id = 999 ;
397+ let result = service_handler. channel_open_failed ( & client_node_id, nonexistent_user_channel_id) ;
398+
399+ assert ! ( result. is_err( ) ) ;
400+ match result. unwrap_err ( ) {
401+ APIError :: APIMisuseError { err } => {
402+ assert ! ( err. contains( "No counterparty state for" ) ) ;
403+ } ,
404+ other => panic ! ( "Unexpected error type: {:?}" , other) ,
405+ }
406+ }
407+
408+ #[ test]
409+ fn channel_open_abandoned ( ) {
410+ let ( service_node_id, client_node_id, service_node, client_node, _) = setup_test_lsps2 ( ) ;
411+
412+ let service_handler = service_node. liquidity_manager . lsps2_service_handler ( ) . unwrap ( ) ;
413+
414+ // Set up a JIT channel
415+ let get_info_request_id = client_node
416+ . liquidity_manager
417+ . lsps2_client_handler ( )
418+ . unwrap ( )
419+ . request_opening_params ( service_node_id, None ) ;
420+ let get_info_request = get_lsps_message ! ( client_node, service_node_id) ;
421+ service_node. liquidity_manager . handle_custom_message ( get_info_request, client_node_id) . unwrap ( ) ;
422+ let _get_info_event = service_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
423+
424+ let raw_opening_params = LSPS2RawOpeningFeeParams {
425+ min_fee_msat : 100 ,
426+ proportional : 21 ,
427+ valid_until : LSPSDateTime :: from_str ( "2035-05-20T08:30:45Z" ) . unwrap ( ) ,
428+ min_lifetime : 144 ,
429+ max_client_to_self_delay : 128 ,
430+ min_payment_size_msat : 1 ,
431+ max_payment_size_msat : 100_000_000 ,
432+ } ;
433+ service_handler
434+ . opening_fee_params_generated (
435+ & client_node_id,
436+ get_info_request_id. clone ( ) ,
437+ vec ! [ raw_opening_params] ,
438+ )
439+ . unwrap ( ) ;
440+
441+ let get_info_response = get_lsps_message ! ( service_node, client_node_id) ;
442+ client_node
443+ . liquidity_manager
444+ . handle_custom_message ( get_info_response, service_node_id)
445+ . unwrap ( ) ;
446+
447+ let opening_fee_params = match client_node. liquidity_manager . next_event ( ) . unwrap ( ) {
448+ LiquidityEvent :: LSPS2Client ( LSPS2ClientEvent :: OpeningParametersReady {
449+ opening_fee_params_menu,
450+ ..
451+ } ) => opening_fee_params_menu. first ( ) . unwrap ( ) . clone ( ) ,
452+ _ => panic ! ( "Unexpected event" ) ,
453+ } ;
454+
455+ let payment_size_msat = Some ( 1_000_000 ) ;
456+ let buy_request_id = client_node
457+ . liquidity_manager
458+ . lsps2_client_handler ( )
459+ . unwrap ( )
460+ . select_opening_params ( service_node_id, payment_size_msat, opening_fee_params. clone ( ) )
461+ . unwrap ( ) ;
462+ let buy_request = get_lsps_message ! ( client_node, service_node_id) ;
463+ service_node. liquidity_manager . handle_custom_message ( buy_request, client_node_id) . unwrap ( ) ;
464+
465+ let _buy_event = service_node. liquidity_manager . next_event ( ) . unwrap ( ) ;
466+ let user_channel_id = 42 ;
467+ let cltv_expiry_delta = 144 ;
468+ let intercept_scid = service_node. channel_manager . get_intercept_scid ( ) ;
469+ let client_trusts_lsp = true ;
470+
471+ service_handler
472+ . invoice_parameters_generated (
473+ & client_node_id,
474+ buy_request_id. clone ( ) ,
475+ intercept_scid,
476+ cltv_expiry_delta,
477+ client_trusts_lsp,
478+ user_channel_id,
479+ )
480+ . unwrap ( ) ;
481+
482+ // Call channel_open_abandoned
483+ service_handler. channel_open_abandoned ( & client_node_id, user_channel_id) . unwrap ( ) ;
484+
485+ // Verify the channel is gone by trying to abandon it again, which should fail
486+ let result = service_handler. channel_open_abandoned ( & client_node_id, user_channel_id) ;
487+ assert ! ( result. is_err( ) ) ;
488+ }
489+
490+ #[ test]
491+ fn channel_open_abandoned_nonexistent_channel ( ) {
492+ let ( _, client_node_id, service_node, _, _) = setup_test_lsps2 ( ) ;
493+ let service_handler = service_node. liquidity_manager . lsps2_service_handler ( ) . unwrap ( ) ;
494+
495+ // Call channel_open_abandoned with a nonexistent user_channel_id
496+ let nonexistent_user_channel_id = 999 ;
497+ let result =
498+ service_handler. channel_open_abandoned ( & client_node_id, nonexistent_user_channel_id) ;
499+ assert ! ( result. is_err( ) ) ;
500+ match result. unwrap_err ( ) {
501+ APIError :: APIMisuseError { err } => {
502+ assert ! ( err. contains( "No counterparty state for" ) ) ;
503+ } ,
504+ other => panic ! ( "Unexpected error type: {:?}" , other) ,
505+ }
506+ }
0 commit comments