-
Notifications
You must be signed in to change notification settings - Fork 465
Release held LSPS2 HTLCs when abandoning JIT opens #4703
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 |
|---|---|---|
|
|
@@ -619,6 +619,14 @@ impl PeerState { | |
| self.needs_persist |= true; | ||
| } | ||
|
|
||
| fn remove_outbound_channel(&mut self, intercept_scid: u64) { | ||
| if self.outbound_channels_by_intercept_scid.remove(&intercept_scid).is_some() { | ||
| self.intercept_scid_by_user_channel_id.retain(|_, scid| *scid != intercept_scid); | ||
| self.intercept_scid_by_channel_id.retain(|_, scid| *scid != intercept_scid); | ||
| self.needs_persist |= true; | ||
| } | ||
| } | ||
|
|
||
| fn prune_pending_requests(&mut self) { | ||
| self.pending_requests.retain(|_, entry| { | ||
| match entry { | ||
|
|
@@ -988,19 +996,19 @@ where | |
| payment_hash: PaymentHash, | ||
| ) -> Result<(), APIError> { | ||
| let event_queue_notifier = self.pending_events.notifier(); | ||
| let mut should_persist = None; | ||
| let should_persist; | ||
|
|
||
| if let Some(counterparty_node_id) = | ||
| self.peer_by_intercept_scid.read().unwrap().get(&intercept_scid) | ||
| { | ||
| let counterparty_node_id = | ||
| self.peer_by_intercept_scid.read().unwrap().get(&intercept_scid).copied(); | ||
| if let Some(counterparty_node_id) = counterparty_node_id { | ||
| let outer_state_lock = self.per_peer_state.read().unwrap(); | ||
| match outer_state_lock.get(counterparty_node_id) { | ||
| match outer_state_lock.get(&counterparty_node_id) { | ||
| Some(inner_state_lock) => { | ||
| let mut peer_state = inner_state_lock.lock().unwrap(); | ||
| if let Some(jit_channel) = | ||
| peer_state.outbound_channels_by_intercept_scid.get_mut(&intercept_scid) | ||
| { | ||
| should_persist = Some(*counterparty_node_id); | ||
| should_persist = Some(counterparty_node_id); | ||
| let htlc = InterceptedHTLC { | ||
| intercept_id, | ||
| expected_outbound_amount_msat, | ||
|
|
@@ -1009,7 +1017,7 @@ where | |
| match jit_channel.htlc_intercepted(htlc) { | ||
| Ok(Some(HTLCInterceptedAction::OpenChannel(open_channel_params))) => { | ||
| let event = LSPS2ServiceEvent::OpenChannel { | ||
| their_network_key: counterparty_node_id.clone(), | ||
| their_network_key: counterparty_node_id, | ||
| amt_to_forward_msat: open_channel_params.amt_to_forward_msat, | ||
| opening_fee_msat: open_channel_params.opening_fee_msat, | ||
| user_channel_id: jit_channel.user_channel_id, | ||
|
|
@@ -1021,7 +1029,7 @@ where | |
| self.channel_manager.get_cm().forward_intercepted_htlc( | ||
| intercept_id, | ||
| &channel_id, | ||
| *counterparty_node_id, | ||
| counterparty_node_id, | ||
| expected_outbound_amount_msat, | ||
| )?; | ||
| }, | ||
|
|
@@ -1038,7 +1046,7 @@ where | |
| self.channel_manager.get_cm().forward_intercepted_htlc( | ||
| intercept_id, | ||
| &channel_id, | ||
| *counterparty_node_id, | ||
| counterparty_node_id, | ||
| amount_to_forward_msat, | ||
| )?; | ||
| } | ||
|
|
@@ -1048,13 +1056,36 @@ where | |
| self.channel_manager | ||
| .get_cm() | ||
| .fail_intercepted_htlc(intercept_id)?; | ||
| peer_state | ||
| .outbound_channels_by_intercept_scid | ||
| peer_state.remove_outbound_channel(intercept_scid); | ||
| let err = e.err; | ||
| drop(peer_state); | ||
| drop(outer_state_lock); | ||
|
Contributor
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. Why do we need these explicit drops? |
||
| self.peer_by_intercept_scid | ||
| .write() | ||
| .unwrap() | ||
| .remove(&intercept_scid); | ||
|
joostjager marked this conversation as resolved.
|
||
| // TODO: cleanup peer_by_intercept_scid | ||
| return Err(APIError::APIMisuseError { err: e.err }); | ||
| if let Err(e) = self.persist_peer_state(counterparty_node_id).await | ||
|
Contributor
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. Not sure we need to eagerly persist here? |
||
| { | ||
| return Err(APIError::APIMisuseError { | ||
| err: format!( | ||
| "Failed to persist peer state for {} after HTLC interception failed with '{}': {}", | ||
| counterparty_node_id, err, e | ||
| ), | ||
| }); | ||
| } | ||
| return Err(APIError::APIMisuseError { err }); | ||
| }, | ||
| } | ||
| } else { | ||
| // `peer_by_intercept_scid` is a separate index from the per-peer | ||
|
Contributor
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. What is the 'per-peer channel map'? In which case would pruning leave an entry in |
||
| // channel map, so pruning can leave the SCID pointing here after | ||
| // the channel entry was removed. | ||
| drop(peer_state); | ||
| drop(outer_state_lock); | ||
| self.peer_by_intercept_scid.write().unwrap().remove(&intercept_scid); | ||
|
joostjager marked this conversation as resolved.
Collaborator
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. Two notes on this branch:
Contributor
Author
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. @tnull what do you think of 2, should we fail back because it did recognize the scid? |
||
| return Err(APIError::APIMisuseError { | ||
| err: format!("No channel found for scid: {}", intercept_scid), | ||
| }); | ||
| } | ||
|
|
||
| peer_state.needs_persist |= should_persist.is_some(); | ||
|
|
@@ -1257,6 +1288,8 @@ where | |
| /// This removes the intercept SCID, any outbound channel state, and associated | ||
| /// channel‐ID mappings for the specified `user_channel_id`, but only while no payment | ||
| /// has been forwarded yet and no channel has been opened on-chain. | ||
| /// Any held HTLCs for the pending flow are failed backwards before the local state | ||
| /// is removed. | ||
| /// | ||
| /// Returns an error if: | ||
| /// - there is no channel matching `user_channel_id`, or | ||
|
|
@@ -1270,7 +1303,7 @@ where | |
| pub async fn channel_open_abandoned( | ||
| &self, counterparty_node_id: &PublicKey, user_channel_id: u128, | ||
| ) -> Result<(), APIError> { | ||
| { | ||
| let intercept_scid = { | ||
| 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 { | ||
|
|
@@ -1292,32 +1325,35 @@ where | |
|
|
||
| let jit_channel = peer_state | ||
| .outbound_channels_by_intercept_scid | ||
| .get(&intercept_scid) | ||
| .get_mut(&intercept_scid) | ||
| .ok_or_else(|| APIError::APIMisuseError { | ||
| err: format!( | ||
| "Failed to map intercept_scid {} for user_channel_id {} to a channel.", | ||
| intercept_scid, user_channel_id, | ||
| ), | ||
| })?; | ||
| err: format!( | ||
| "Failed to map intercept_scid {} for user_channel_id {} to a channel.", | ||
| intercept_scid, user_channel_id, | ||
| ), | ||
| })?; | ||
|
|
||
| let is_pending = matches!( | ||
| jit_channel.state, | ||
| OutboundJITChannelState::PendingInitialPayment { .. } | ||
| | OutboundJITChannelState::PendingChannelOpen { .. } | ||
| ); | ||
| let intercepted_htlcs = match &mut jit_channel.state { | ||
|
Contributor
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. Why do we need all this seemingly unrelated refactoring? |
||
| OutboundJITChannelState::PendingInitialPayment { payment_queue } | ||
| | OutboundJITChannelState::PendingChannelOpen { payment_queue, .. } => payment_queue.clear(), | ||
| _ => { | ||
| return Err(APIError::APIMisuseError { | ||
| err: "Cannot abandon channel open after channel creation or payment forwarding" | ||
| .to_string(), | ||
| }); | ||
| }, | ||
| }; | ||
|
|
||
| if !is_pending { | ||
| return Err(APIError::APIMisuseError { | ||
| err: "Cannot abandon channel open after channel creation or payment forwarding" | ||
| .to_string(), | ||
| }); | ||
| for htlc in intercepted_htlcs { | ||
| // A missing intercept has already been released; still remove the LSPS2 state. | ||
| let _ = self.channel_manager.get_cm().fail_intercepted_htlc(htlc.intercept_id); | ||
| } | ||
|
|
||
| peer_state.intercept_scid_by_user_channel_id.remove(&user_channel_id); | ||
| peer_state.outbound_channels_by_intercept_scid.remove(&intercept_scid); | ||
| peer_state.intercept_scid_by_channel_id.retain(|_, &mut scid| scid != intercept_scid); | ||
| peer_state.needs_persist |= true; | ||
| } | ||
| peer_state.remove_outbound_channel(intercept_scid); | ||
| intercept_scid | ||
| }; | ||
|
|
||
| self.peer_by_intercept_scid.write().unwrap().remove(&intercept_scid); | ||
|
|
||
| self.persist_peer_state(*counterparty_node_id).await.map_err(|e| { | ||
| APIError::APIMisuseError { | ||
|
|
||
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.
Why do we need all these changes from reference to copy suddenly?