Skip to content

Commit 772b21c

Browse files
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 c4d23bc commit 772b21c

File tree

1 file changed

+99
-1
lines changed

1 file changed

+99
-1
lines changed

Diff for: lightning-liquidity/src/lsps2/service.rs

+99-1
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::HTLCDestination;
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;
@@ -1005,6 +1005,104 @@ where
10051005
Ok(())
10061006
}
10071007

1008+
/// Abandons a channel open attempt by pruning all state related to the channel open.
1009+
///
1010+
/// This function should be used when a channel open attempt is to be abandoned entirely,
1011+
/// without resetting the state for a potential payment retry. It removes the intercept SCID
1012+
/// mapping along with any outbound channel state and related channel ID mappings associated with
1013+
/// the specified `user_channel_id`.
1014+
pub fn channel_open_abandoned(
1015+
&self, counterparty_node_id: &PublicKey, user_channel_id: u128,
1016+
) -> Result<(), APIError> {
1017+
let outer_state_lock = self.per_peer_state.read().unwrap();
1018+
let inner_state_lock =
1019+
outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError {
1020+
err: format!("No counterparty state for: {}", counterparty_node_id),
1021+
})?;
1022+
let mut peer_state = inner_state_lock.lock().unwrap();
1023+
1024+
let intercept_scid = peer_state
1025+
.intercept_scid_by_user_channel_id
1026+
.remove(&user_channel_id)
1027+
.ok_or_else(|| APIError::APIMisuseError {
1028+
err: format!("Could not find a channel with user_channel_id {}", user_channel_id),
1029+
})?;
1030+
1031+
peer_state.outbound_channels_by_intercept_scid.remove(&intercept_scid);
1032+
1033+
peer_state.intercept_scid_by_channel_id.retain(|_, &mut scid| scid != intercept_scid);
1034+
1035+
Ok(())
1036+
}
1037+
1038+
/// Used to fail intercepted HTLCs backwards when a channel open attempt ultimately fails.
1039+
///
1040+
/// This function should be called after receiving an [`LSPS2ServiceEvent::OpenChannel`] event
1041+
/// but only if the channel could not be successfully established. It resets the JIT channel
1042+
/// state so that the payer may try the payment again.
1043+
///
1044+
/// [`LSPS2ServiceEvent::OpenChannel`]: crate::lsps2::event::LSPS2ServiceEvent::OpenChannel
1045+
pub fn channel_open_failed(
1046+
&self, counterparty_node_id: &PublicKey, user_channel_id: u128,
1047+
) -> Result<(), APIError> {
1048+
let outer_state_lock = self.per_peer_state.read().unwrap();
1049+
1050+
let inner_state_lock =
1051+
outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError {
1052+
err: format!("No counterparty state for: {}", counterparty_node_id),
1053+
})?;
1054+
1055+
let mut peer_state = inner_state_lock.lock().unwrap();
1056+
1057+
let intercept_scid = peer_state
1058+
.intercept_scid_by_user_channel_id
1059+
.get(&user_channel_id)
1060+
.copied()
1061+
.ok_or_else(|| APIError::APIMisuseError {
1062+
err: format!("Could not find a channel with user_channel_id {}", user_channel_id),
1063+
})?;
1064+
1065+
let jit_channel = peer_state
1066+
.outbound_channels_by_intercept_scid
1067+
.get_mut(&intercept_scid)
1068+
.ok_or_else(|| APIError::APIMisuseError {
1069+
err: format!(
1070+
"Failed to map the stored intercept_scid {} for the provided user_channel_id {} to a channel.",
1071+
intercept_scid, user_channel_id,
1072+
),
1073+
})?;
1074+
1075+
jit_channel.state = match &jit_channel.state {
1076+
OutboundJITChannelState::PendingChannelOpen { payment_queue, .. } => {
1077+
let mut queue = payment_queue.lock().unwrap();
1078+
let payment_hashes: Vec<_> = queue
1079+
.clear()
1080+
.into_iter()
1081+
.map(|htlc| htlc.payment_hash)
1082+
.collect::<std::collections::HashSet<_>>()
1083+
.into_iter()
1084+
.collect();
1085+
1086+
for payment_hash in payment_hashes {
1087+
self.channel_manager.get_cm().fail_htlc_backwards_with_reason(
1088+
&payment_hash,
1089+
FailureCode::TemporaryNodeFailure,
1090+
);
1091+
}
1092+
OutboundJITChannelState::PendingInitialPayment {
1093+
payment_queue: payment_queue.clone(),
1094+
}
1095+
},
1096+
_ => {
1097+
return Err(APIError::APIMisuseError {
1098+
err: "Channel is not in the PendingChannelOpen state.".to_string(),
1099+
})
1100+
},
1101+
};
1102+
1103+
Ok(())
1104+
}
1105+
10081106
/// Forward [`Event::ChannelReady`] event parameters into this function.
10091107
///
10101108
/// Will forward the intercepted HTLC if it matches a channel

0 commit comments

Comments
 (0)