Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cf49410

Browse files
committedMay 28, 2025
add failed & abandoned HTLC open handlers
Add two LSPS2Service methods: 'Abandoned' prunes all channel open state. 'Failed' resets JIT channel to fail HTLCs. It allows a retry on channel open. Closes #3479.
1 parent 5688166 commit cf49410

File tree

1 file changed

+123
-1
lines changed

1 file changed

+123
-1
lines changed
 

‎lightning-liquidity/src/lsps2/service.rs

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::prelude::{new_hash_map, HashMap};
3232
use crate::sync::{Arc, Mutex, MutexGuard, RwLock};
3333

3434
use lightning::events::HTLCHandlingFailureType;
35-
use lightning::ln::channelmanager::{AChannelManager, InterceptId};
35+
use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId};
3636
use lightning::ln::msgs::{ErrorAction, LightningError};
3737
use lightning::ln::types::ChannelId;
3838
use lightning::util::errors::APIError;
@@ -985,6 +985,128 @@ where
985985
Ok(())
986986
}
987987

988+
/// Abandons a pending JIT‐open flow for `user_channel_id`, removing all local state.
989+
///
990+
/// This removes the intercept SCID, any outbound channel state, and associated
991+
/// channel‐ID mappings for the specified `user_channel_id`, but only while no payment
992+
/// has been forwarded yet and no channel has been opened on-chain.
993+
///
994+
/// Returns an error if:
995+
/// - there is no channel matching `user_channel_id`, or
996+
/// - a payment has already been forwarded or a channel has already been opened
997+
///
998+
/// Note: this does *not* close or roll back any on‐chain channel which may already
999+
/// have been opened. The caller must call this before or instead of initiating the channel
1000+
/// open, as it only affects the local LSPS2 state and doesn't affect any channels that
1001+
/// might already exist on-chain. Any pending channel open attempts must be managed
1002+
/// separately.
1003+
pub fn channel_open_abandoned(
1004+
&self, counterparty_node_id: &PublicKey, user_channel_id: u128,
1005+
) -> Result<(), APIError> {
1006+
let outer_state_lock = self.per_peer_state.read().unwrap();
1007+
let inner_state_lock =
1008+
outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError {
1009+
err: format!("No counterparty state for: {}", counterparty_node_id),
1010+
})?;
1011+
let mut peer_state = inner_state_lock.lock().unwrap();
1012+
1013+
let intercept_scid = peer_state
1014+
.intercept_scid_by_user_channel_id
1015+
.get(&user_channel_id)
1016+
.copied()
1017+
.ok_or_else(|| APIError::APIMisuseError {
1018+
err: format!("Could not find a channel with user_channel_id {}", user_channel_id),
1019+
})?;
1020+
1021+
let jit_channel = peer_state
1022+
.outbound_channels_by_intercept_scid
1023+
.get(&intercept_scid)
1024+
.ok_or_else(|| APIError::APIMisuseError {
1025+
err: format!(
1026+
"Failed to map intercept_scid {} for user_channel_id {} to a channel.",
1027+
intercept_scid, user_channel_id,
1028+
),
1029+
})?;
1030+
1031+
let is_pending = matches!(
1032+
jit_channel.state,
1033+
OutboundJITChannelState::PendingInitialPayment { .. }
1034+
| OutboundJITChannelState::PendingChannelOpen { .. }
1035+
);
1036+
1037+
if !is_pending {
1038+
return Err(APIError::APIMisuseError {
1039+
err: "Cannot abandon channel open after channel creation or payment forwarding"
1040+
.to_string(),
1041+
});
1042+
}
1043+
1044+
peer_state.intercept_scid_by_user_channel_id.remove(&user_channel_id);
1045+
peer_state.outbound_channels_by_intercept_scid.remove(&intercept_scid);
1046+
peer_state.intercept_scid_by_channel_id.retain(|_, &mut scid| scid != intercept_scid);
1047+
1048+
Ok(())
1049+
}
1050+
1051+
/// Used to fail intercepted HTLCs backwards when a channel open attempt ultimately fails.
1052+
///
1053+
/// This function should be called after receiving an [`LSPS2ServiceEvent::OpenChannel`] event
1054+
/// but only if the channel could not be successfully established. It resets the JIT channel
1055+
/// state so that the payer may try the payment again.
1056+
///
1057+
/// [`LSPS2ServiceEvent::OpenChannel`]: crate::lsps2::event::LSPS2ServiceEvent::OpenChannel
1058+
pub fn channel_open_failed(
1059+
&self, counterparty_node_id: &PublicKey, user_channel_id: u128,
1060+
) -> Result<(), APIError> {
1061+
let outer_state_lock = self.per_peer_state.read().unwrap();
1062+
1063+
let inner_state_lock =
1064+
outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError {
1065+
err: format!("No counterparty state for: {}", counterparty_node_id),
1066+
})?;
1067+
1068+
let mut peer_state = inner_state_lock.lock().unwrap();
1069+
1070+
let intercept_scid = peer_state
1071+
.intercept_scid_by_user_channel_id
1072+
.get(&user_channel_id)
1073+
.copied()
1074+
.ok_or_else(|| APIError::APIMisuseError {
1075+
err: format!("Could not find a channel with user_channel_id {}", user_channel_id),
1076+
})?;
1077+
1078+
let jit_channel = peer_state
1079+
.outbound_channels_by_intercept_scid
1080+
.get_mut(&intercept_scid)
1081+
.ok_or_else(|| APIError::APIMisuseError {
1082+
err: format!(
1083+
"Failed to map intercept_scid {} for user_channel_id {} to a channel.",
1084+
intercept_scid, user_channel_id,
1085+
),
1086+
})?;
1087+
1088+
if let OutboundJITChannelState::PendingChannelOpen { payment_queue, .. } =
1089+
&mut jit_channel.state
1090+
{
1091+
let intercepted_htlcs = payment_queue.clear();
1092+
for htlc in intercepted_htlcs {
1093+
self.channel_manager.get_cm().fail_htlc_backwards_with_reason(
1094+
&htlc.payment_hash,
1095+
FailureCode::TemporaryNodeFailure,
1096+
);
1097+
}
1098+
1099+
jit_channel.state = OutboundJITChannelState::PendingInitialPayment {
1100+
payment_queue: PaymentQueue::new(),
1101+
};
1102+
Ok(())
1103+
} else {
1104+
Err(APIError::APIMisuseError {
1105+
err: "Channel is not in the PendingChannelOpen state.".to_string(),
1106+
})
1107+
}
1108+
}
1109+
9881110
/// Forward [`Event::ChannelReady`] event parameters into this function.
9891111
///
9901112
/// Will forward the intercepted HTLC if it matches a channel

0 commit comments

Comments
 (0)
Please sign in to comment.