Skip to content

Commit df3a0d3

Browse files
authored
Merge pull request lightningdevkit#4703 from joostjager/lsps2-abandon-fail-intercepted-htlcs
Release held LSPS2 HTLCs when abandoning JIT opens
2 parents 31bb3de + ccc8b55 commit df3a0d3

2 files changed

Lines changed: 139 additions & 16 deletions

File tree

lightning-liquidity/src/lsps2/service.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,8 @@ where
13161316
/// This removes the intercept SCID, any outbound channel state, and associated
13171317
/// channel‐ID mappings for the specified `user_channel_id`, but only while no payment
13181318
/// has been forwarded yet and no channel has been opened on-chain.
1319+
/// Any held HTLCs for the pending flow are failed backwards before the local state
1320+
/// is removed.
13191321
///
13201322
/// Returns an error if:
13211323
/// - there is no channel matching `user_channel_id`, or
@@ -1351,25 +1353,27 @@ where
13511353

13521354
let jit_channel = peer_state
13531355
.outbound_channels_by_intercept_scid
1354-
.get(&intercept_scid)
1356+
.get_mut(&intercept_scid)
13551357
.ok_or_else(|| APIError::APIMisuseError {
1356-
err: format!(
1357-
"Failed to map intercept_scid {} for user_channel_id {} to a channel.",
1358-
intercept_scid, user_channel_id,
1359-
),
1360-
})?;
1358+
err: format!(
1359+
"Failed to map intercept_scid {} for user_channel_id {} to a channel.",
1360+
intercept_scid, user_channel_id,
1361+
),
1362+
})?;
13611363

1362-
let is_pending = matches!(
1363-
jit_channel.state,
1364-
OutboundJITChannelState::PendingInitialPayment { .. }
1365-
| OutboundJITChannelState::PendingChannelOpen { .. }
1366-
);
1364+
let intercepted_htlcs = match &mut jit_channel.state {
1365+
OutboundJITChannelState::PendingInitialPayment { payment_queue }
1366+
| OutboundJITChannelState::PendingChannelOpen { payment_queue, .. } => payment_queue.clear(),
1367+
_ => {
1368+
return Err(APIError::APIMisuseError {
1369+
err: "Cannot abandon channel open after channel creation or payment forwarding"
1370+
.to_string(),
1371+
});
1372+
},
1373+
};
13671374

1368-
if !is_pending {
1369-
return Err(APIError::APIMisuseError {
1370-
err: "Cannot abandon channel open after channel creation or payment forwarding"
1371-
.to_string(),
1372-
});
1375+
for htlc in intercepted_htlcs {
1376+
let _ = self.channel_manager.get_cm().fail_intercepted_htlc(htlc.intercept_id);
13731377
}
13741378

13751379
peer_state.intercept_scid_by_user_channel_id.remove(&user_channel_id);

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,125 @@ fn channel_open_abandoned() {
682682
assert!(result.is_err());
683683
}
684684

685+
#[test]
686+
fn channel_open_abandoned_releases_intercepted_htlcs() {
687+
let chanmon_cfgs = create_chanmon_cfgs(3);
688+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
689+
let mut service_node_config = test_default_channel_config();
690+
service_node_config.htlc_interception_flags = HTLCInterceptionFlags::ToInterceptSCIDs as u8;
691+
692+
let mut client_node_config = test_default_channel_config();
693+
client_node_config.channel_config.accept_underpaying_htlcs = true;
694+
695+
let node_chanmgrs = create_node_chanmgrs(
696+
3,
697+
&node_cfgs,
698+
&[Some(service_node_config), Some(client_node_config), None],
699+
);
700+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
701+
let (lsps_nodes, promise_secret) = setup_test_lsps2_nodes_with_payer(nodes);
702+
let LSPSNodesWithPayer { ref service_node, ref client_node, ref payer_node } = lsps_nodes;
703+
704+
let payer_node_id = payer_node.node.get_our_node_id();
705+
let service_node_id = service_node.inner.node.get_our_node_id();
706+
let client_node_id = client_node.inner.node.get_our_node_id();
707+
708+
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();
709+
create_chan_between_nodes_with_value(&payer_node, &service_node.inner, 2_000_000, 100_000);
710+
711+
let intercept_scid = service_node.node.get_intercept_scid();
712+
let user_channel_id = 42u128;
713+
let cltv_expiry_delta: u32 = 144;
714+
let payment_size_msat = Some(1_000_000);
715+
let fee_base_msat: u64 = 1_000;
716+
717+
execute_lsps2_dance(
718+
&lsps_nodes,
719+
intercept_scid,
720+
user_channel_id,
721+
cltv_expiry_delta,
722+
promise_secret,
723+
payment_size_msat,
724+
fee_base_msat,
725+
);
726+
727+
let invoice = create_jit_invoice(
728+
&client_node,
729+
service_node_id,
730+
intercept_scid,
731+
cltv_expiry_delta,
732+
payment_size_msat,
733+
"channel-open-abandoned-cleanup",
734+
3600,
735+
)
736+
.unwrap();
737+
738+
payer_node
739+
.node
740+
.pay_for_bolt11_invoice(
741+
&invoice,
742+
PaymentId(invoice.payment_hash().0),
743+
None,
744+
OptionalBolt11PaymentParams::default(),
745+
)
746+
.unwrap();
747+
748+
check_added_monitors(&payer_node, 1);
749+
let events = payer_node.node.get_and_clear_pending_msg_events();
750+
let ev = SendEvent::from_event(events[0].clone());
751+
service_node.inner.node.handle_update_add_htlc(payer_node_id, &ev.msgs[0]);
752+
do_commitment_signed_dance(&service_node.inner, &payer_node, &ev.commitment_msg, false, true);
753+
service_node.inner.node.process_pending_htlc_forwards();
754+
755+
let events = service_node.inner.node.get_and_clear_pending_events();
756+
assert_eq!(events.len(), 1);
757+
let intercept_id = match &events[0] {
758+
Event::HTLCIntercepted {
759+
intercept_id,
760+
requested_next_hop_scid,
761+
payment_hash,
762+
expected_outbound_amount_msat,
763+
..
764+
} => {
765+
assert_eq!(*requested_next_hop_scid, intercept_scid);
766+
service_handler
767+
.htlc_intercepted(
768+
*requested_next_hop_scid,
769+
*intercept_id,
770+
*expected_outbound_amount_msat,
771+
*payment_hash,
772+
)
773+
.unwrap();
774+
*intercept_id
775+
},
776+
other => panic!("Expected HTLCIntercepted, got {:?}", other),
777+
};
778+
779+
match service_node.liquidity_manager.next_event().unwrap() {
780+
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel { .. }) => {},
781+
other => panic!("Unexpected event: {:?}", other),
782+
};
783+
784+
service_handler.channel_open_abandoned(&client_node_id, user_channel_id).unwrap();
785+
786+
let res = service_node.inner.node.fail_intercepted_htlc(intercept_id);
787+
assert!(
788+
res.is_err(),
789+
"channel_open_abandoned must release the intercepted HTLC via fail_intercepted_htlc, but the entry is still pending: {:?}",
790+
res,
791+
);
792+
793+
let events = service_node.inner.node.get_and_clear_pending_events();
794+
assert_eq!(events.len(), 1);
795+
match &events[0] {
796+
Event::HTLCHandlingFailed {
797+
failure_type: HTLCHandlingFailureType::InvalidForward { requested_forward_scid },
798+
..
799+
} => assert_eq!(*requested_forward_scid, intercept_scid),
800+
other => panic!("Expected HTLCHandlingFailed, got {:?}", other),
801+
}
802+
}
803+
685804
#[test]
686805
fn channel_open_abandoned_nonexistent_channel() {
687806
let chanmon_cfgs = create_chanmon_cfgs(2);

0 commit comments

Comments
 (0)