Skip to content

Commit dd32fe7

Browse files
authored
Merge pull request #4677 from tnull/2026-06-10-lsps2-channel-open-failed
Release LSPS2 intercepted HTLCs on open failure
2 parents c9260ee + 6b75e5a commit dd32fe7

2 files changed

Lines changed: 123 additions & 6 deletions

File tree

lightning-liquidity/src/lsps2/service.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use crate::utils::async_poll::dummy_waker;
4242

4343
use lightning::chain::chaininterface::{BroadcasterInterface, TransactionType};
4444
use lightning::events::HTLCHandlingFailureType;
45-
use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId};
45+
use lightning::ln::channelmanager::{AChannelManager, InterceptId};
4646
use lightning::ln::msgs::{ErrorAction, LightningError};
4747
use lightning::ln::types::ChannelId;
4848
use lightning::util::errors::APIError;
@@ -1375,10 +1375,8 @@ where
13751375
{
13761376
let intercepted_htlcs = payment_queue.clear();
13771377
for htlc in intercepted_htlcs {
1378-
self.channel_manager.get_cm().fail_htlc_backwards_with_reason(
1379-
&htlc.payment_hash,
1380-
FailureCode::TemporaryNodeFailure,
1381-
);
1378+
// A missing intercept has already been released; still reset this LSPS2 state.
1379+
let _ = self.channel_manager.get_cm().fail_intercepted_htlc(htlc.intercept_id);
13821380
}
13831381

13841382
jit_channel.state = OutboundJITChannelState::PendingInitialPayment {

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use common::{
77
get_lsps_message, LSPSNodes, LSPSNodesWithPayer, LiquidityNode,
88
};
99

10-
use lightning::events::{ClosureReason, Event};
10+
use lightning::events::{ClosureReason, Event, HTLCHandlingFailureType};
1111
use lightning::get_event_msg;
1212
use lightning::ln::channelmanager::{
1313
OptionalBolt11PaymentParams, PaymentId, TrustedChannelFeatures,
@@ -453,6 +453,125 @@ fn channel_open_failed() {
453453
};
454454
}
455455

456+
#[test]
457+
fn channel_open_failed_releases_intercepted_htlcs() {
458+
let chanmon_cfgs = create_chanmon_cfgs(3);
459+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
460+
let mut service_node_config = test_default_channel_config();
461+
service_node_config.htlc_interception_flags = HTLCInterceptionFlags::ToInterceptSCIDs as u8;
462+
463+
let mut client_node_config = test_default_channel_config();
464+
client_node_config.channel_config.accept_underpaying_htlcs = true;
465+
466+
let node_chanmgrs = create_node_chanmgrs(
467+
3,
468+
&node_cfgs,
469+
&[Some(service_node_config), Some(client_node_config), None],
470+
);
471+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
472+
let (lsps_nodes, promise_secret) = setup_test_lsps2_nodes_with_payer(nodes);
473+
let LSPSNodesWithPayer { ref service_node, ref client_node, ref payer_node } = lsps_nodes;
474+
475+
let payer_node_id = payer_node.node.get_our_node_id();
476+
let service_node_id = service_node.inner.node.get_our_node_id();
477+
let client_node_id = client_node.inner.node.get_our_node_id();
478+
479+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
480+
create_chan_between_nodes_with_value(&payer_node, &service_node.inner, 2_000_000, 100_000);
481+
482+
let intercept_scid = service_node.node.get_intercept_scid();
483+
let user_channel_id = 42u128;
484+
let cltv_expiry_delta: u32 = 144;
485+
let payment_size_msat = Some(1_000_000);
486+
let fee_base_msat: u64 = 1_000;
487+
488+
execute_lsps2_dance(
489+
&lsps_nodes,
490+
intercept_scid,
491+
user_channel_id,
492+
cltv_expiry_delta,
493+
promise_secret,
494+
payment_size_msat,
495+
fee_base_msat,
496+
);
497+
498+
let invoice = create_jit_invoice(
499+
&client_node,
500+
service_node_id,
501+
intercept_scid,
502+
cltv_expiry_delta,
503+
payment_size_msat,
504+
"channel-open-failed-cleanup",
505+
3600,
506+
)
507+
.unwrap();
508+
509+
payer_node
510+
.node
511+
.pay_for_bolt11_invoice(
512+
&invoice,
513+
PaymentId(invoice.payment_hash().0),
514+
None,
515+
OptionalBolt11PaymentParams::default(),
516+
)
517+
.unwrap();
518+
519+
check_added_monitors(&payer_node, 1);
520+
let events = payer_node.node.get_and_clear_pending_msg_events();
521+
let ev = SendEvent::from_event(events[0].clone());
522+
service_node.inner.node.handle_update_add_htlc(payer_node_id, &ev.msgs[0]);
523+
do_commitment_signed_dance(&service_node.inner, &payer_node, &ev.commitment_msg, false, true);
524+
service_node.inner.node.process_pending_htlc_forwards();
525+
526+
let events = service_node.inner.node.get_and_clear_pending_events();
527+
assert_eq!(events.len(), 1);
528+
let intercept_id = match &events[0] {
529+
Event::HTLCIntercepted {
530+
intercept_id,
531+
requested_next_hop_scid,
532+
payment_hash,
533+
expected_outbound_amount_msat,
534+
..
535+
} => {
536+
assert_eq!(*requested_next_hop_scid, intercept_scid);
537+
service_handler
538+
.htlc_intercepted(
539+
*requested_next_hop_scid,
540+
*intercept_id,
541+
*expected_outbound_amount_msat,
542+
*payment_hash,
543+
)
544+
.unwrap();
545+
*intercept_id
546+
},
547+
other => panic!("Expected HTLCIntercepted, got {:?}", other),
548+
};
549+
550+
match service_node.liquidity_manager.next_event().unwrap() {
551+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel { .. }) => {},
552+
other => panic!("Unexpected event: {:?}", other),
553+
};
554+
555+
service_handler.channel_open_failed(&client_node_id, user_channel_id).unwrap();
556+
557+
let res = service_node.inner.node.fail_intercepted_htlc(intercept_id);
558+
assert!(
559+
res.is_err(),
560+
"channel_open_failed must release the intercepted HTLC via fail_intercepted_htlc, but the entry is still pending: {:?}",
561+
res,
562+
);
563+
564+
let events = service_node.inner.node.get_and_clear_pending_events();
565+
assert_eq!(events.len(), 1);
566+
match &events[0] {
567+
Event::HTLCHandlingFailed {
568+
failure_type: HTLCHandlingFailureType::InvalidForward { requested_forward_scid },
569+
..
570+
} => assert_eq!(*requested_forward_scid, intercept_scid),
571+
other => panic!("Expected HTLCHandlingFailed, got {:?}", other),
572+
}
573+
}
574+
456575
#[test]
457576
fn channel_open_failed_nonexistent_channel() {
458577
let chanmon_cfgs = create_chanmon_cfgs(2);

0 commit comments

Comments
 (0)