Skip to content

Commit 3a60d53

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 3a60d53

File tree

1 file changed

+100
-1
lines changed

1 file changed

+100
-1
lines changed

lightning-liquidity/src/lsps2/service.rs

+100-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
1212
use alloc::string::{String, ToString};
1313
use alloc::vec::Vec;
14+
use lightning::util::hash_tables::HashSet;
1415

1516
use core::ops::Deref;
1617
use core::sync::atomic::{AtomicUsize, Ordering};
@@ -32,7 +33,7 @@ use crate::prelude::{new_hash_map, HashMap};
3233
use crate::sync::{Arc, Mutex, MutexGuard, RwLock};
3334

3435
use lightning::events::HTLCDestination;
35-
use lightning::ln::channelmanager::{AChannelManager, InterceptId};
36+
use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId};
3637
use lightning::ln::msgs::{ErrorAction, LightningError};
3738
use lightning::ln::types::ChannelId;
3839
use lightning::util::errors::APIError;
@@ -1005,6 +1006,104 @@ where
10051006
Ok(())
10061007
}
10071008

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

0 commit comments

Comments
 (0)