What changed, and why it matters
This commit adds a new notification event called SpliceFailed to the Lightning Dev Kit library. It tells users when a channel splice (a way to resize a Lightning channel) failed before any funding transaction was signed, and lists which bitcoin inputs and outputs can be reused. There is no direct security fix here; it is a user-facing bookkeeping improvement.
No immediate security action required. Treat as a normal feature/API addition. Review downstream event handlers to ensure SpliceFailed is handled so that contributed UTXOs are correctly recycled, which is the intended operational benefit.
Security signals we found
No vulnerability pattern in diff (no cryptographic, authorization, or memory-safety change)
Adds event notification for failed splice lifecycle state
Includes persistence/replay semantics consistent with other events
No vendor security framing in commit message or diff
Evidence from the diff
The change introduces Event::SpliceFailed in lightning/src/events/mod.rs, with serialization (Writeable) and deserialization (MaybeReadable) support using TLV fields. The event carries channel_id, user_channel_id, counterparty_node_id, an optional abandoned_funding_txo, optional channel_type, and vectors of contributed_inputs (OutPoint) and contributed_outputs (TxOut). It is purely additive and does not alter existing channel logic, cryptographic checks, or state machine behavior.
Changed components
lightning/src/events/mod.rsEvent enum serialization/deserializationInspect captured patch +79 / −1
diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs
index 25669e2..d8a1a18 100644
--- a/lightning/src/events/mod.rs
+++ b/lightning/src/events/mod.rs
@@ -49,7 +49,7 @@ use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use bitcoin::script::ScriptBuf;
use bitcoin::secp256k1::PublicKey;
-use bitcoin::{OutPoint, Transaction};
+use bitcoin::{OutPoint, Transaction, TxOut};
use core::ops::Deref;
#[allow(unused_imports)]
@@ -1533,6 +1533,40 @@ pub enum Event {
/// features that the channel was opened with, but in the future splices may change them.
channel_type: ChannelTypeFeatures,
},
+ /// Used to indicate that a splice for the given `channel_id` has failed.
+ ///
+ /// This event may be emitted if a splice fails after it has been initiated but prior to signing
+ /// any negotiated funding transaction.
+ ///
+ /// Any UTXOs contributed to be spent by the funding transaction may be reused and will be
+ /// given in `contributed_inputs`.
+ ///
+ /// # Failure Behavior and Persistence
+ /// This event will eventually be replayed after failures-to-handle (i.e., the event handler
+ /// returning `Err(ReplayEvent ())`) and will be persisted across restarts.
+ SpliceFailed {
+ /// The `channel_id` of the channel for which the splice failed.
+ channel_id: ChannelId,
+ /// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound
+ /// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if
+ /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
+ /// `user_channel_id` will be randomized for an inbound channel.
+ ///
+ /// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel
+ /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
+ /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
+ user_channel_id: u128,
+ /// The `node_id` of the channel counterparty.
+ counterparty_node_id: PublicKey,
+ /// The outpoint of the channel's splice funding transaction, if one was created.
+ abandoned_funding_txo: Option<OutPoint>,
+ /// The features that this channel will operate with, if available.
+ channel_type: Option<ChannelTypeFeatures>,
+ /// UTXOs spent as inputs contributed to the splice transaction.
+ contributed_inputs: Vec<OutPoint>,
+ /// Outputs contributed to the splice transaction.
+ contributed_outputs: Vec<TxOut>,
+ },
/// Used to indicate to the user that they can abandon the funding transaction and recycle the
/// inputs for another purpose.
///
@@ -2275,6 +2309,26 @@ impl Writeable for Event {
(9, new_funding_txo, required),
});
},
+ &Event::SpliceFailed {
+ ref channel_id,
+ ref user_channel_id,
+ ref counterparty_node_id,
+ ref abandoned_funding_txo,
+ ref channel_type,
+ ref contributed_inputs,
+ ref contributed_outputs,
+ } => {
+ 52u8.write(writer)?;
+ write_tlv_fields!(writer, {
+ (1, channel_id, required),
+ (3, channel_type, option),
+ (5, user_channel_id, required),
+ (7, counterparty_node_id, required),
+ (9, abandoned_funding_txo, option),
+ (11, *contributed_inputs, optional_vec),
+ (13, *contributed_outputs, optional_vec),
+ });
+ },
// Note that, going forward, all new events must only write data inside of
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
// data via `write_tlv_fields`.
@@ -2877,6 +2931,30 @@ impl MaybeReadable for Event {
};
f()
},
+ 52u8 => {
+ let mut f = || {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
+ (1, channel_id, required),
+ (3, channel_type, option),
+ (5, user_channel_id, required),
+ (7, counterparty_node_id, required),
+ (9, abandoned_funding_txo, option),
+ (11, contributed_inputs, optional_vec),
+ (13, contributed_outputs, optional_vec),
+ });
+
+ Ok(Some(Event::SpliceFailed {
+ channel_id: channel_id.0.unwrap(),
+ user_channel_id: user_channel_id.0.unwrap(),
+ counterparty_node_id: counterparty_node_id.0.unwrap(),
+ abandoned_funding_txo,
+ channel_type,
+ contributed_inputs: contributed_inputs.unwrap_or_default(),
+ contributed_outputs: contributed_outputs.unwrap_or_default(),
+ }))
+ };
+ f()
+ },
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
// reads.
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.