Skip to content

Conversation

jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Sep 17, 2025

When a splice has been negotiated, it remains pending until the funding transaction has been confirmed and locked by both sides. Emit an Event::SplicePending when it reaches this state. At this point, the inputs used for the splice cannot be reused except for an RBF attempt. Once the splice is locked, an Event::DiscardFunding will be emitted for any unsuccessful candidates.

Similarly, a splice may fail before a splice has finished negotiation for various reasons. Emit an Event::SpliceFailed in these cases so the user may reuse the inputs.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Sep 17, 2025

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@jkczyz
Copy link
Contributor Author

jkczyz commented Sep 17, 2025

Looking for high-level feedback on the approach and if all scenarios were covered. Specifically, is SpliceFailed sufficient to let a user know if an input can be reused? Seems for an RBF attempt they could have used an input that was used by a previous attempt that has had a SplicePending emitted. So it could be reused for another RBF attempt, IIUC, but not for splicing another channel, for instance.

Copy link

codecov bot commented Sep 17, 2025

Codecov Report

❌ Patch coverage is 77.72228% with 223 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.77%. Comparing base (eeb5b14) to head (957b742).

Files with missing lines Patch % Lines
lightning/src/ln/channelmanager.rs 66.76% 101 Missing and 15 partials ⚠️
lightning/src/ln/channel.rs 70.26% 78 Missing and 2 partials ⚠️
lightning/src/ln/interactivetxs.rs 72.72% 11 Missing and 1 partial ⚠️
lightning/src/ln/functional_test_utils.rs 78.78% 7 Missing ⚠️
lightning/src/events/mod.rs 90.00% 0 Missing and 4 partials ⚠️
lightning/src/ln/splicing_tests.rs 98.45% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4077      +/-   ##
==========================================
+ Coverage   88.73%   88.77%   +0.03%     
==========================================
  Files         180      180              
  Lines      135622   136350     +728     
  Branches   135622   136350     +728     
==========================================
+ Hits       120346   121045     +699     
+ Misses      12505    12502       -3     
- Partials     2771     2803      +32     
Flag Coverage Δ
fuzzing 21.62% <9.70%> (-0.10%) ⬇️
tests 88.61% <77.72%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from 0db24f3 to 41c1e60 Compare September 18, 2025 02:03
Comment on lines 11890 to 12031
let splice_funding = self.validate_splice_ack(msg)?;
let splice_funding = self.validate_splice_ack(msg).map_err(|err| {
let splice_failed = SpliceFundingFailed {
channel_id: self.context.channel_id,
counterparty_node_id: self.context.counterparty_node_id,
user_channel_id: self.context.user_id,
funding_txo: None,
channel_type: None,
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
};
(err, splice_failed)
})?;
Copy link
Contributor Author

@jkczyz jkczyz Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit will likely need to be re-worked to have validate_splice_ack return Option<SpliceFundingFailed> as part of its error. But it seems we never clear pending_splice.funding_negotiation here.

@wpaulino Should we? I'd imagine if we ever emit Event::SpliceFailed then pending_splice.funding_negotiation should no longer be set.

Comment on lines 11920 to 12074
ChannelError::WarnAndDisconnect(format!(
let splice_failed = SpliceFundingFailed {
channel_id: self.context.channel_id,
counterparty_node_id: self.context.counterparty_node_id,
user_channel_id: self.context.user_id,
funding_txo: splice_funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()),
channel_type: Some(splice_funding.get_channel_type().clone()),
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
};
let channel_error = ChannelError::WarnAndDisconnect(format!(
"Failed to start interactive transaction construction, {:?}",
err
))
));
(channel_error, splice_failed)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here.


pub(super) struct InteractiveTxConstructor {
state_machine: StateMachine,
is_initiator: bool,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be able to get this from the StateMachine, but not if it transitioned to NegotiationAborted, it seems.

Comment on lines 1777 to 1778
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue with populating these is if we are in FundingNegotiation::AwaitingSignatures, then we'd need to get them from ChannelContext::interactive_tx_signing_session, which may not have been set yet in FundedChannel::funding_tx_constructed depending on the error.

Comment on lines 2004 to 2018
let signing_session = interactive_tx_constructor.into_signing_session();
let commitment_signed = chan.context.funding_tx_constructed(
let commitment_signed_result = chan.context.funding_tx_constructed(
&mut funding,
signing_session,
true,
chan.holder_commitment_point.next_transaction_number(),
&&logger,
)?;
);

// This must be set even if returning an Err. Otherwise,
// fail_interactive_tx_negotiation won't produce a SpliceFailed event.
pending_splice.funding_negotiation =
Some(FundingNegotiation::AwaitingSignatures { funding });

return Ok(commitment_signed);
return commitment_signed_result;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, specifically, we may not have set ChannelContext::interactive_tx_signing_session for some errors.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the status of this? IMO we should land it early so we can write tests and discover where we're missing coverage.


/// Information about a splice funding negotiation that has been completed.
/// This is returned from channel operations and converted to an Event::SplicePending in ChannelManager.
pub struct SpliceFundingNegotiated {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh, just return the Event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... our pattern has been to only create events in ChannelManager. Though we can drop the first three fields and take them from the ChannelContext in ChannelManager as is done for other events.

/// Information about a splice funding negotiation that has been completed.
/// This is returned from channel operations and converted to an Event::SplicePending in ChannelManager.
pub struct SpliceFundingNegotiated {
/// The channel_id of the channel being spliced.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol, these comments are great, they're exactly what I expect from an intern, they say absolutely nothing but meet the not-even-a-requirement for things having docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I'll take a pass and clean up Claude's work. Didn't really give it much prompting to be honest.

/// Input outpoints contributed to the splice transaction.
contributed_inputs: Vec<OutPoint>,
/// Outputs contributed to the splice transaction.
contributed_outputs: Vec<TxOut>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I believe we discussed on the call last week, but we should include information about other splices still pending.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the idea was to only include inputs that can be unlocked. I need to update this still.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we won't have RBF in the initial release, this doesn't need to be included yet.

@jkczyz
Copy link
Contributor Author

jkczyz commented Sep 23, 2025

What's the status of this? IMO we should land it early so we can write tests and discover where we're missing coverage.

This depends on #4097 now.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from 41c1e60 to a01b467 Compare September 24, 2025 16:38
@jkczyz jkczyz marked this pull request as ready for review September 24, 2025 16:39
Comment on lines 1915 to 1892
let splice_funding_failed = funding_negotiation_opt
.filter(|funding_negotiation| funding_negotiation.is_initiator())
.map(|funding_negotiation| {
// Create SpliceFundingFailed for the aborted splice
let (funding_txo, channel_type) = match &funding_negotiation {
FundingNegotiation::ConstructingTransaction { funding, .. } => {
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone()))
},
FundingNegotiation::AwaitingSignatures { funding, .. } => {
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone()))
},
FundingNegotiation::AwaitingAck { .. } => {
(None, None)
},
};

SpliceFundingFailed {
channel_id: funded_channel.context.channel_id,
counterparty_node_id: funded_channel.context.counterparty_node_id,
user_channel_id: funded_channel.context.user_id,
funding_txo,
channel_type,
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: In tx_abort handling for funded channels, when a splice negotiation is aborted during the ConstructingTransaction state, the contributed inputs and outputs are not extracted from the interactive_tx_constructor before creating the SpliceFundingFailed event. The code sets contributed_inputs and contributed_outputs to empty vectors, but if the local node had already contributed inputs/outputs to the transaction construction, this information should be preserved so users know which UTXOs they can reuse. The interactive_tx_constructor contains inputs_to_contribute and outputs_to_contribute fields that should be extracted before the constructor is dropped.

Suggested change
let splice_funding_failed = funding_negotiation_opt
.filter(|funding_negotiation| funding_negotiation.is_initiator())
.map(|funding_negotiation| {
// Create SpliceFundingFailed for the aborted splice
let (funding_txo, channel_type) = match &funding_negotiation {
FundingNegotiation::ConstructingTransaction { funding, .. } => {
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone()))
},
FundingNegotiation::AwaitingSignatures { funding, .. } => {
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()), Some(funding.get_channel_type().clone()))
},
FundingNegotiation::AwaitingAck { .. } => {
(None, None)
},
};
SpliceFundingFailed {
channel_id: funded_channel.context.channel_id,
counterparty_node_id: funded_channel.context.counterparty_node_id,
user_channel_id: funded_channel.context.user_id,
funding_txo,
channel_type,
contributed_inputs: Vec::new(),
contributed_outputs: Vec::new(),
}
});
let splice_funding_failed = funding_negotiation_opt
.filter(|funding_negotiation| funding_negotiation.is_initiator())
.map(|funding_negotiation| {
// Create SpliceFundingFailed for the aborted splice
let (funding_txo, channel_type, contributed_inputs, contributed_outputs) = match &funding_negotiation {
FundingNegotiation::ConstructingTransaction { funding, interactive_tx_constructor, .. } => {
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()),
Some(funding.get_channel_type().clone()),
interactive_tx_constructor.inputs_to_contribute.clone(),
interactive_tx_constructor.outputs_to_contribute.clone())
},
FundingNegotiation::AwaitingSignatures { funding, .. } => {
(funding.get_funding_txo().map(|txo| txo.into_bitcoin_outpoint()),
Some(funding.get_channel_type().clone()),
Vec::new(),
Vec::new())
},
FundingNegotiation::AwaitingAck { .. } => {
(None, None, Vec::new(), Vec::new())
},
};
SpliceFundingFailed {
channel_id: funded_channel.context.channel_id,
counterparty_node_id: funded_channel.context.counterparty_node_id,
user_channel_id: funded_channel.context.user_id,
funding_txo,
channel_type,
contributed_inputs,
contributed_outputs,
}
});

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@jkczyz
Copy link
Contributor Author

jkczyz commented Sep 24, 2025

I pushed four new commits immediately after the SplicePending commits that allows us to extract inputs / outputs from interactivetx.rs structs for SpliceFailed events. There's now a NegotiationError, which wraps an AbortReason along with the inputs and outputs. There are still a few places where the inputs and outputs need to be populated in channel.rs, though.

@jkczyz
Copy link
Contributor Author

jkczyz commented Sep 24, 2025

Specifically, see the into_negotiation_error methods.

@jkczyz jkczyz force-pushed the 2025-09-splice-events branch 2 times, most recently from 8ff58b9 to 3a08a0e Compare September 25, 2025 00:40
@jkczyz
Copy link
Contributor Author

jkczyz commented Sep 25, 2025

Opened #4123 based on some offline discussion, which this PR is now based on. It includes NegotiationError and related refactoring. However, it makes a more limited change to interactivetx.rs. Instead of adding complexity to the state transition code, it simply maintains the inputs and outputs from InteractiveTxConstructor -- instead of popping them -- so they can be used to form SpliceFailed events.

@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from 1165217 to 9842c35 Compare October 7, 2025 23:28
Comment on lines 10407 to 10546
if let Some(splice_funding_failed) = splice_funding_failed {
let pending_events = &mut self.pending_events.lock().unwrap();
pending_events.push_back((events::Event::SpliceFailed {
channel_id: msg.channel_id,
counterparty_node_id,
user_channel_id: chan.context().get_user_id(),
funding_txo: splice_funding_failed.funding_txo,
channel_type: splice_funding_failed.channel_type.clone(),
contributed_inputs: splice_funding_failed.contributed_inputs,
contributed_outputs: splice_funding_failed.contributed_outputs,
}, None));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, added a fixup to persist in this case.

Copy link
Contributor Author

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some test coverage.

&[ExpectedCloseEvent {
channel_id: Some(channel_id),
discard_funding: false,
splice_failed: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and below, not sure if there's a better way to test for this. Problem was having a SpliceFailed event show up with a ChannelClosed event.

Comment on lines +867 to +1237
let event = get_event!(initiator, Event::SpliceFailed);
match event {
Event::SpliceFailed { contributed_inputs, .. } => {
assert_eq!(contributed_inputs.len(), 1);
assert_eq!(contributed_inputs[0], contribution.inputs()[0].outpoint());
},
_ => panic!("Expected Event::SpliceFailed"),
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm only doing some minimal checks here, so let me know if you want anything else covered.

Comment on lines 6890 to 7009
FundingNegotiation::ConstructingTransaction {
interactive_tx_constructor,
..
} => interactive_tx_constructor.into_contributed_inputs_and_outputs(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I think the SpliceFailed tests may only be covering this case.

Comment on lines 6870 to 6886
self.quiescent_action.take().and_then(|quiescent_action| match quiescent_action {
QuiescentAction::Splice(instructions) => {
self.context.channel_state.clear_awaiting_quiescence();
let (inputs, outputs) = instructions.into_contributed_inputs_and_outputs();
Some(SpliceFundingFailed {
funding_txo: None,
channel_type: None,
contributed_inputs: inputs,
contributed_outputs: outputs,
})
},
#[cfg(any(test, fuzzing))]
_ => {
self.quiescent_action = Some(quiescent_action);
None
},
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter doesn't seem to like this. Says I should use map instead of and_then, but that won't work for the catch-all arm. May need to use if let to get around it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the and_then with a match on the taken Option<QuiescentAction>.

@jkczyz jkczyz requested review from TheBlueMatt and wpaulino October 7, 2025 23:40
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a test of splice failure on disconnect/reload?

@wpaulino
Copy link
Contributor

wpaulino commented Oct 8, 2025

Can we get a test of splice failure on disconnect/reload?

Isn't that already covered by test_splice_state_reset_on_disconnect? It may be worth rebasing this on #4079 though.

@jkczyz
Copy link
Contributor Author

jkczyz commented Oct 8, 2025

Can we get a test of splice failure on disconnect/reload?

Isn't that already covered by test_splice_state_reset_on_disconnect? It may be worth rebasing this on #4079 though.

Yeah, though I realized that we aren't handling the reload case. Since we only persist FundingNegotiation::AwaitingSignatures, we'd need to generate a SpliceFailed event when persisting whenever the other variants are present. Is that preferable to persisting those variants (or at least the contributed inputs / outputs) and producing the event upon reload?

@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from 9842c35 to c2c6fdf Compare October 8, 2025 21:43
@jkczyz
Copy link
Contributor Author

jkczyz commented Oct 8, 2025

Can we get a test of splice failure on disconnect/reload?

Isn't that already covered by test_splice_state_reset_on_disconnect? It may be worth rebasing this on #4079 though.

Yeah, though I realized that we aren't handling the reload case. Since we only persist FundingNegotiation::AwaitingSignatures, we'd need to generate a SpliceFailed event when persisting whenever the other variants are present. Is that preferable to persisting those variants (or at least the contributed inputs / outputs) and producing the event upon reload?

As discussed offline, ended up persisting SpliceFailed events when not in FundingNegotiation::AwaitingSignatures. Need to allocate a Vec when this is ever the case because mutexes not playing well with iterators.

@jkczyz jkczyz force-pushed the 2025-09-splice-events branch 3 times, most recently from bd15e74 to 3ad8b15 Compare October 9, 2025 21:44
@jkczyz jkczyz requested a review from TheBlueMatt October 9, 2025 21:45
jkczyz added 8 commits October 9, 2025 17:17
Once a splice has been negotiated and its funding transaction has been
broadcast, it is considered pending until both parties have seen enough
confirmations to consider the funding locked. Add an event used to
indicate this.
Once a splice has been negotiated and its funding transaction has been
broadcast, emit a SplicePending event. Once this occurs, the inputs
contributed to the splice cannot be reused except by an RBF attempt.
Once a splice has been successfully initiated, but prior to signing any
negotiated funding transaction, it may fail. Add an event used to
indicate this and which UTXOs can be reused.
When interactive transaction construction fails during splice funding
negotiation, emit Event::SpliceFailed to notify users of the failure
such that they can reclaim any contributed UTXOs.
When a tx_abort message is successfully processed for a funded channel
with an active splice negotiation, emit Event::SpliceFailed to notify
users that the splice operation was aborted by the counterparty.
When a channel has a pending splice and is shutdown, generate a
SpliceFailed event when necessary. This allows users to reclaim any
contributed UTXOs.
Since quiescence is terminated upon disconnection, any outstanding
splice negotiation should result in emitting a SpliceFailed event as
long as we haven't reached FundingNegotiation::AwaitingSignatures. This
may occur if we explicitly disconnect the peer (e.g., when failing to
process splice_ack) or if the connection is lost.
When ChannelMangager::splice_channel returns Ok, a QuiescentAction is
set. Since this is persisted with a FundedChannel, an Ok result should
trigger persistence.
@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from 3ad8b15 to f51a24b Compare October 9, 2025 22:38
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

let res = self.internal_tx_abort(&counterparty_node_id, msg);
let persist = match &res {
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
Err(_) => NotifyOption::SkipPersistHandleEvents,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong. We should persist as a result of generating the new event.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the event is emitted for the Ok case. Either:

(1) we already sent our own tx_abort because we had a ChannelError::Abort earlier and they are responding with a tx_abort as per the protocol, or
(2) they are sending the initial tx_abort, so we are responding in kind.

For (2) we could return ChannelError::Abort instead, I suppose. But it's not really an error. Doing so would be as a way to re-use that code path.

return None;
}

self.pending_splice
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Care to DRY this up with the copy in reset_pending_splice_state?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... FWIW, I think we'd need to use a macro. That one takes the FundingNegotiation and uses into_ methods (consuming self) while this one leaves the FundingNegotiation and uses to_ methods (making copies). I guess we could simply clone it and use the into_ methods. But most of the contained data doesn't implement Clone and isn't actually needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed the macro approach since it turned out to be straightforward.

let peer_state = &mut *peer_state_lock;
for chan in peer_state.channel_by_id.values().filter_map(Channel::as_funded) {
if let Some(splice_funding_failed) = chan.maybe_splice_funding_failed() {
self.pending_events.lock().unwrap().push_back((
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why push this? We already have a loop a few lines down that iterates over the pending events and writes them one at a time, just put this after that loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also written as a TLV later.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, still so awkward. If we're gonna push it at least keep a single lock over pending_events the whole time so its not unlocked with the bogs event in between.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lock-order violation. We could alloc a Vec and extend the existing one, but I assumed we wanted to avoid that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The peer_states Vec that is built a few lines down to hold all the per-peer locks can be moved up as well to address that, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah. My earlier attempt was retaking the lock, which is why I had moved this code above it. But I can just use peer_states.iter() to avoid that. Pushed a new version that does this.

This also made me realize we can actually use Iterable since the mutex problem mentioned in #4077 (comment) doesn't exist when using peer_states.iter(). However, we'd need a second TLV because we can't chain and Iterator returning references with one returning values, IIUC.

I attempted this but ran into tests failing with ShortRead errors. Haven't quite debugged it, but I pushed to a separate branch in case it's worth considering the apporach: jkczyz@cfeae18. There's some extra copies made to do the count unfortunately.

@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from f51a24b to bf5f224 Compare October 10, 2025 02:42
Similarly to when a peer is disconnected, when a node is reloaded any
splice that hasn't reaching FundingNegotiation::AwaitingSignatures will
be reset. This should produce a SpliceFailed event. However, since other
FundingNegotiation variants are not persisted, the data to produced the
SpliceFailed event upon reload is lost. Therefore, opportunistically
persist a SpliceFailed event for these cases such that it is available
upon reload.
Update the ChannelManager::splice_channel docs to reflect the current
state of the implementation and splicing-related events.
@jkczyz jkczyz force-pushed the 2025-09-splice-events branch from bf5f224 to 957b742 Compare October 10, 2025 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

4 participants