-
Notifications
You must be signed in to change notification settings - Fork 402
LSPS2: Fail (or abandon) intercepted HTLCs if LSP channel open fails #3712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
|
||
use alloc::string::{String, ToString}; | ||
use alloc::vec::Vec; | ||
use lightning::util::hash_tables::HashSet; | ||
|
||
use core::ops::Deref; | ||
use core::sync::atomic::{AtomicUsize, Ordering}; | ||
|
@@ -32,12 +33,11 @@ use crate::prelude::{new_hash_map, HashMap}; | |
use crate::sync::{Arc, Mutex, MutexGuard, RwLock}; | ||
|
||
use lightning::events::HTLCDestination; | ||
use lightning::ln::channelmanager::{AChannelManager, InterceptId}; | ||
use lightning::ln::channelmanager::{AChannelManager, FailureCode, InterceptId}; | ||
use lightning::ln::msgs::{ErrorAction, LightningError}; | ||
use lightning::ln::types::ChannelId; | ||
use lightning::util::errors::APIError; | ||
use lightning::util::logger::Level; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't remove this whitespace, it's there to group the imports. |
||
use lightning_types::payment::PaymentHash; | ||
|
||
use bitcoin::secp256k1::PublicKey; | ||
|
@@ -1005,6 +1005,123 @@ where | |
Ok(()) | ||
} | ||
|
||
/// Abandons a channel open attempt by pruning all state related to the channel open. | ||
/// | ||
/// This function should be used when a channel open attempt is to be abandoned entirely, | ||
/// without resetting the state for a potential payment retry. It removes the intercept SCID | ||
/// mapping along with any outbound channel state and related channel ID mappings associated with | ||
/// the specified `user_channel_id`. | ||
pub fn channel_open_abandoned( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think to make this a bit safer, it should probably fail if we're already at Also, what would happen if this is called after the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now when
You mean if the channel is already opened, but our state machine hasn’t caught up yet and still thinks we're at There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes. I think it would be possible if the user had already initiated a channel open but hasn't gotten the |
||
&self, counterparty_node_id: &PublicKey, user_channel_id: u128, | ||
) -> Result<(), APIError> { | ||
let outer_state_lock = self.per_peer_state.read().unwrap(); | ||
let inner_state_lock = | ||
outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError { | ||
err: format!("No counterparty state for: {}", counterparty_node_id), | ||
})?; | ||
let mut peer_state = inner_state_lock.lock().unwrap(); | ||
|
||
let intercept_scid = peer_state | ||
.intercept_scid_by_user_channel_id | ||
.remove(&user_channel_id) | ||
.ok_or_else(|| APIError::APIMisuseError { | ||
err: format!("Could not find a channel with user_channel_id {}", user_channel_id), | ||
})?; | ||
|
||
if let Some(jit_channel) = | ||
peer_state.outbound_channels_by_intercept_scid.get(&intercept_scid) | ||
{ | ||
if !matches!( | ||
jit_channel.state, | ||
OutboundJITChannelState::PendingInitialPayment { .. } | ||
| OutboundJITChannelState::PendingChannelOpen { .. } | ||
) { | ||
return Err(APIError::APIMisuseError { | ||
err: "Cannot abandon channel open after channel creation or payment forwarding" | ||
.to_string(), | ||
}); | ||
} | ||
} | ||
|
||
peer_state.outbound_channels_by_intercept_scid.remove(&intercept_scid); | ||
|
||
peer_state.intercept_scid_by_channel_id.retain(|_, &mut scid| scid != intercept_scid); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Used to fail intercepted HTLCs backwards when a channel open attempt ultimately fails. | ||
/// | ||
/// This function should be called after receiving an [`LSPS2ServiceEvent::OpenChannel`] event | ||
/// but only if the channel could not be successfully established. It resets the JIT channel | ||
/// state so that the payer may try the payment again. | ||
/// | ||
/// [`LSPS2ServiceEvent::OpenChannel`]: crate::lsps2::event::LSPS2ServiceEvent::OpenChannel | ||
pub fn channel_open_failed( | ||
&self, counterparty_node_id: &PublicKey, user_channel_id: u128, | ||
) -> Result<(), APIError> { | ||
let outer_state_lock = self.per_peer_state.read().unwrap(); | ||
|
||
let inner_state_lock = | ||
outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError { | ||
err: format!("No counterparty state for: {}", counterparty_node_id), | ||
})?; | ||
|
||
let mut peer_state = inner_state_lock.lock().unwrap(); | ||
|
||
let intercept_scid = peer_state | ||
.intercept_scid_by_user_channel_id | ||
.get(&user_channel_id) | ||
.copied() | ||
.ok_or_else(|| APIError::APIMisuseError { | ||
err: format!("Could not find a channel with user_channel_id {}", user_channel_id), | ||
})?; | ||
|
||
let jit_channel = peer_state | ||
.outbound_channels_by_intercept_scid | ||
.get_mut(&intercept_scid) | ||
.ok_or_else(|| APIError::APIMisuseError { | ||
err: format!( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting is off here. |
||
"Failed to map the stored intercept_scid {} for the provided user_channel_id {} to a channel.", | ||
intercept_scid, user_channel_id, | ||
), | ||
})?; | ||
|
||
if !matches!(jit_channel.state, OutboundJITChannelState::PendingChannelOpen { .. }) { | ||
return Err(APIError::APIMisuseError { | ||
err: "Channel is not in the PendingChannelOpen state.".to_string(), | ||
}); | ||
} | ||
|
||
let payment_queue_arc = | ||
if let OutboundJITChannelState::PendingChannelOpen { payment_queue, .. } = | ||
&jit_channel.state | ||
{ | ||
Arc::clone(payment_queue) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a rebase as post-#3509 the payment queue isn't held in an |
||
} else { | ||
unreachable!() | ||
}; | ||
let mut queue = payment_queue_arc.lock().unwrap(); | ||
let payment_hashes: Vec<_> = queue | ||
.clear() | ||
.into_iter() | ||
.map(|htlc| htlc.payment_hash) | ||
.collect::<HashSet<_>>() | ||
.into_iter() | ||
.collect(); | ||
for payment_hash in payment_hashes { | ||
self.channel_manager | ||
.get_cm() | ||
.fail_htlc_backwards_with_reason(&payment_hash, FailureCode::TemporaryNodeFailure); | ||
} | ||
|
||
jit_channel.state = OutboundJITChannelState::PendingInitialPayment { | ||
payment_queue: Arc::clone(&payment_queue_arc), | ||
}; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Forward [`Event::ChannelReady`] event parameters into this function. | ||
/// | ||
/// Will forward the intercepted HTLC if it matches a channel | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this import down to the other
lightning
imports.