Skip to content

Commit 90ea998

Browse files
joostjagerTheBlueMatt
authored andcommitted
Fail held HTLCs on LSPS2 abandon
Drain queued intercepted HTLCs before removing pending LSPS2 JIT channel state in channel_open_abandoned. Add a real interception regression test that verifies the held HTLC is no longer pending after the abandon call. Backport of ccc8b55 Silent conflicts resolved in: * lightning-liquidity/tests/lsps2_integration_tests.rs
1 parent e1e3eb6 commit 90ea998

2 files changed

Lines changed: 140 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
@@ -1257,6 +1257,8 @@ where
12571257
/// This removes the intercept SCID, any outbound channel state, and associated
12581258
/// channel‐ID mappings for the specified `user_channel_id`, but only while no payment
12591259
/// has been forwarded yet and no channel has been opened on-chain.
1260+
/// Any held HTLCs for the pending flow are failed backwards before the local state
1261+
/// is removed.
12601262
///
12611263
/// Returns an error if:
12621264
/// - there is no channel matching `user_channel_id`, or
@@ -1292,25 +1294,27 @@ where
12921294

12931295
let jit_channel = peer_state
12941296
.outbound_channels_by_intercept_scid
1295-
.get(&intercept_scid)
1297+
.get_mut(&intercept_scid)
12961298
.ok_or_else(|| APIError::APIMisuseError {
1297-
err: format!(
1298-
"Failed to map intercept_scid {} for user_channel_id {} to a channel.",
1299-
intercept_scid, user_channel_id,
1300-
),
1301-
})?;
1299+
err: format!(
1300+
"Failed to map intercept_scid {} for user_channel_id {} to a channel.",
1301+
intercept_scid, user_channel_id,
1302+
),
1303+
})?;
13021304

1303-
let is_pending = matches!(
1304-
jit_channel.state,
1305-
OutboundJITChannelState::PendingInitialPayment { .. }
1306-
| OutboundJITChannelState::PendingChannelOpen { .. }
1307-
);
1305+
let intercepted_htlcs = match &mut jit_channel.state {
1306+
OutboundJITChannelState::PendingInitialPayment { payment_queue }
1307+
| OutboundJITChannelState::PendingChannelOpen { payment_queue, .. } => payment_queue.clear(),
1308+
_ => {
1309+
return Err(APIError::APIMisuseError {
1310+
err: "Cannot abandon channel open after channel creation or payment forwarding"
1311+
.to_string(),
1312+
});
1313+
},
1314+
};
13081315

1309-
if !is_pending {
1310-
return Err(APIError::APIMisuseError {
1311-
err: "Cannot abandon channel open after channel creation or payment forwarding"
1312-
.to_string(),
1313-
});
1316+
for htlc in intercepted_htlcs {
1317+
let _ = self.channel_manager.get_cm().fail_intercepted_htlc(htlc.intercept_id);
13141318
}
13151319

13161320
peer_state.intercept_scid_by_user_channel_id.remove(&user_channel_id);

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,126 @@ fn channel_open_abandoned() {
696696
assert!(result.is_err());
697697
}
698698

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

0 commit comments

Comments
 (0)