From 734bc6a66e9bc6ea5e5549dd7926c91c2758de3e Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 19 Nov 2025 09:36:07 -0600 Subject: [PATCH 1/2] Add funding redeem script to `ChannelDetails` and `ChannelPending` event Original context and motivation comes from here: https://github.com/lightningdevkit/ldk-node/pull/677#discussion_r2505405974 When splicing-in, the default case is our channel utxo + our wallet utxos being combined. This works great however, it can give our wallet issues calculating fees after the fact because our wallet needs to know about our channel's utxo. We currently have it's outpoint and satoshi value available, but not its output script so we are unable to construct the TxOut for the channel. This adds the redeem script to the `ChannelDetails` and `ChannelPending` event which gives us enough information to be able to construct it. --- fuzz/src/router.rs | 1 + lightning/src/events/mod.rs | 9 +++++++++ lightning/src/ln/chan_utils.rs | 15 ++++++++++----- lightning/src/ln/channel_state.rs | 28 ++++++++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 3 +++ lightning/src/routing/router.rs | 10 ++++++++++ 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index a4fb00e61f9..af29a0221a9 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -229,6 +229,7 @@ pub fn do_test(data: &[u8], out: Out) { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0, }), + funding_redeem_script: None, channel_type: None, short_channel_id: Some(scid), inbound_scid_alias: None, diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 9f7e4c5620d..3c52016d853 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1413,6 +1413,10 @@ pub enum Event { /// /// Will be `None` for channels created prior to LDK version 0.0.122. channel_type: Option, + /// The witness script that is used to lock the channel's funding output to commitment transactions. + /// + /// This field will be `None` for objects serialized with LDK versions prior to 0.2.0. + funding_redeem_script: Option, }, /// Used to indicate that a channel with the given `channel_id` is ready to be used. This event /// is emitted when @@ -2234,6 +2238,7 @@ impl Writeable for Event { ref counterparty_node_id, ref funding_txo, ref channel_type, + ref funding_redeem_script, } => { 31u8.write(writer)?; write_tlv_fields!(writer, { @@ -2243,6 +2248,7 @@ impl Writeable for Event { (4, former_temporary_channel_id, required), (6, counterparty_node_id, required), (8, funding_txo, required), + (9, funding_redeem_script, option), }); }, &Event::ConnectionNeeded { .. } => { @@ -2815,6 +2821,7 @@ impl MaybeReadable for Event { let mut counterparty_node_id = RequiredWrapper(None); let mut funding_txo = RequiredWrapper(None); let mut channel_type = None; + let mut funding_redeem_script = None; read_tlv_fields!(reader, { (0, channel_id, required), (1, channel_type, option), @@ -2822,6 +2829,7 @@ impl MaybeReadable for Event { (4, former_temporary_channel_id, required), (6, counterparty_node_id, required), (8, funding_txo, required), + (9, funding_redeem_script, option), }); Ok(Some(Event::ChannelPending { @@ -2831,6 +2839,7 @@ impl MaybeReadable for Event { counterparty_node_id: counterparty_node_id.0.unwrap(), funding_txo: funding_txo.0.unwrap(), channel_type, + funding_redeem_script, })) }; f() diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index e759a4b54cb..13b39f6cabd 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1120,12 +1120,17 @@ impl ChannelTransactionParameters { } } - #[rustfmt::skip] pub(crate) fn make_funding_redeemscript(&self) -> ScriptBuf { - make_funding_redeemscript( - &self.holder_pubkeys.funding_pubkey, - &self.counterparty_parameters.as_ref().unwrap().pubkeys.funding_pubkey - ) + self.make_funding_redeemscript_opt().unwrap() + } + + pub(crate) fn make_funding_redeemscript_opt(&self) -> Option { + self.counterparty_parameters.as_ref().map(|p| { + make_funding_redeemscript( + &self.holder_pubkeys.funding_pubkey, + &p.pubkeys.funding_pubkey, + ) + }) } /// Returns the counterparty's pubkeys. diff --git a/lightning/src/ln/channel_state.rs b/lightning/src/ln/channel_state.rs index c28b4687631..81a7cb4755e 100644 --- a/lightning/src/ln/channel_state.rs +++ b/lightning/src/ln/channel_state.rs @@ -450,6 +450,10 @@ pub struct ChannelDetails { /// /// This field is empty for objects serialized with LDK versions prior to 0.0.122. pub pending_outbound_htlcs: Vec, + /// The witness script that is used to lock the channel's funding output to commitment transactions. + /// + /// This field will be `None` for objects serialized with LDK versions prior to 0.2.0. + pub funding_redeem_script: Option, } impl ChannelDetails { @@ -475,6 +479,21 @@ impl ChannelDetails { self.short_channel_id.or(self.outbound_scid_alias) } + /// Gets the funding output for this channel, if available. + /// + /// During a splice, the funding output will change and this value will be updated + /// after the splice transaction has reached sufficient confirmations and we've + /// exchanged `splice_locked` messages. + pub fn get_funding_output(&self) -> Option { + match self.funding_redeem_script.as_ref() { + None => None, + Some(redeem_script) => Some(bitcoin::TxOut { + value: bitcoin::Amount::from_sat(self.channel_value_satoshis), + script_pubkey: redeem_script.to_p2wsh(), + }), + } + } + pub(super) fn from_channel( channel: &Channel, best_block_height: u32, latest_features: InitFeatures, fee_estimator: &LowerBoundedFeeEstimator, @@ -509,6 +528,9 @@ impl ChannelDetails { outbound_htlc_maximum_msat: context.get_counterparty_htlc_maximum_msat(funding), }, funding_txo: funding.get_funding_txo(), + funding_redeem_script: funding + .channel_transaction_parameters + .make_funding_redeemscript_opt(), // Note that accept_channel (or open_channel) is always the first message, so // `have_received_message` indicates that type negotiation has completed. channel_type: if context.have_received_message() { @@ -583,6 +605,7 @@ impl_writeable_tlv_based!(ChannelDetails, { (41, channel_shutdown_state, option), (43, pending_inbound_htlcs, optional_vec), (45, pending_outbound_htlcs, optional_vec), + (47, funding_redeem_script, option), (_unused, user_channel_id, (static_value, _user_channel_id_low.unwrap_or(0) as u128 | ((_user_channel_id_high.unwrap_or(0) as u128) << 64) )), @@ -627,6 +650,7 @@ mod tests { use crate::{ chain::transaction::OutPoint, ln::{ + chan_utils::make_funding_redeemscript, channel_state::{ InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails, @@ -658,6 +682,10 @@ mod tests { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 1, }), + funding_redeem_script: Some(make_funding_redeemscript( + &PublicKey::from_slice(&[2; 33]).unwrap(), + &PublicKey::from_slice(&[2; 33]).unwrap(), + )), channel_type: None, short_channel_id: None, outbound_scid_alias: None, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 632d897043e..82919a4982a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -3491,6 +3491,8 @@ macro_rules! emit_channel_pending_event { ($locked_events: expr, $channel: expr) => { if $channel.context.should_emit_channel_pending_event() { let funding_txo = $channel.funding.get_funding_txo().unwrap(); + let funding_redeem_script = + Some($channel.funding.channel_transaction_parameters.make_funding_redeemscript()); $locked_events.push_back(( events::Event::ChannelPending { channel_id: $channel.context.channel_id(), @@ -3499,6 +3501,7 @@ macro_rules! emit_channel_pending_event { user_channel_id: $channel.context.get_user_id(), funding_txo: funding_txo.into_bitcoin_outpoint(), channel_type: Some($channel.funding.get_channel_type().clone()), + funding_redeem_script, }, None, )); diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 77396c783e3..8ea3ea068b3 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -3893,6 +3893,7 @@ mod tests { use crate::blinded_path::BlindedHop; use crate::chain::transaction::OutPoint; use crate::crypto::chacha20::ChaCha20; + use crate::ln::chan_utils::make_funding_redeemscript; use crate::ln::channel_state::{ChannelCounterparty, ChannelDetails, ChannelShutdownState}; use crate::ln::channelmanager; use crate::ln::msgs::{UnsignedChannelUpdate, MAX_VALUE_MSAT}; @@ -3950,6 +3951,10 @@ mod tests { outbound_htlc_maximum_msat: None, }, funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), + funding_redeem_script: Some(make_funding_redeemscript( + &PublicKey::from_slice(&[2; 33]).unwrap(), + &PublicKey::from_slice(&[2; 33]).unwrap(), + )), channel_type: None, short_channel_id, outbound_scid_alias: None, @@ -9250,6 +9255,7 @@ pub(crate) mod bench_utils { use std::io::Read; use crate::chain::transaction::OutPoint; + use crate::ln::chan_utils::make_funding_redeemscript; use crate::ln::channel_state::{ChannelCounterparty, ChannelShutdownState}; use crate::ln::channelmanager; use crate::ln::types::ChannelId; @@ -9345,6 +9351,10 @@ pub(crate) mod bench_utils { funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), + funding_redeem_script: Some(make_funding_redeemscript( + &PublicKey::from_slice(&[2; 33]).unwrap(), + &PublicKey::from_slice(&[2; 33]).unwrap(), + )), channel_type: None, short_channel_id: Some(1), inbound_scid_alias: None, From 6e6e4c2702d44013793775372a3b8feeb4dcecd2 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 19 Nov 2025 13:41:07 -0600 Subject: [PATCH 2/2] Add funding redeem script to `SplicePending' event --- lightning/src/events/mod.rs | 6 ++++++ lightning/src/ln/channel.rs | 5 +++++ lightning/src/ln/channelmanager.rs | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 3c52016d853..b9c4b1ca1ef 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1536,6 +1536,8 @@ pub enum Event { /// The features that this channel will operate with. Currently, these will be the same /// features that the channel was opened with, but in the future splices may change them. channel_type: ChannelTypeFeatures, + /// The witness script that is used to lock the channel's funding output to commitment transactions. + new_funding_redeem_script: ScriptBuf, }, /// Used to indicate that a splice for the given `channel_id` has failed. /// @@ -2313,6 +2315,7 @@ impl Writeable for Event { ref counterparty_node_id, ref new_funding_txo, ref channel_type, + ref new_funding_redeem_script, } => { 50u8.write(writer)?; write_tlv_fields!(writer, { @@ -2321,6 +2324,7 @@ impl Writeable for Event { (5, user_channel_id, required), (7, counterparty_node_id, required), (9, new_funding_txo, required), + (11, new_funding_redeem_script, required), }); }, &Event::SpliceFailed { @@ -2936,6 +2940,7 @@ impl MaybeReadable for Event { (5, user_channel_id, required), (7, counterparty_node_id, required), (9, new_funding_txo, required), + (11, new_funding_redeem_script, required), }); Ok(Some(Event::SplicePending { @@ -2944,6 +2949,7 @@ impl MaybeReadable for Event { counterparty_node_id: counterparty_node_id.0.unwrap(), new_funding_txo: new_funding_txo.0.unwrap(), channel_type: channel_type.0.unwrap(), + new_funding_redeem_script: new_funding_redeem_script.0.unwrap(), })) }; f() diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 62b108fd20a..e47b5492efd 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -6853,6 +6853,9 @@ pub struct SpliceFundingNegotiated { /// The features that this channel will operate with. pub channel_type: ChannelTypeFeatures, + + /// The redeem script of the funding output. + pub funding_redeem_script: ScriptBuf, } /// Information about a splice funding negotiation that has failed. @@ -8936,12 +8939,14 @@ where let funding_txo = funding.get_funding_txo().expect("funding outpoint should be set"); let channel_type = funding.get_channel_type().clone(); + let funding_redeem_script = funding.get_funding_redeemscript(); pending_splice.negotiated_candidates.push(funding); let splice_negotiated = SpliceFundingNegotiated { funding_txo: funding_txo.into_bitcoin_outpoint(), channel_type, + funding_redeem_script, }; let splice_locked = pending_splice.check_get_splice_locked( diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 82919a4982a..6bab5911a26 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6459,6 +6459,8 @@ where user_channel_id: chan.context.get_user_id(), new_funding_txo: splice_negotiated.funding_txo, channel_type: splice_negotiated.channel_type, + new_funding_redeem_script: splice_negotiated + .funding_redeem_script, }, None, )); @@ -9615,6 +9617,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ user_channel_id: channel.context.get_user_id(), new_funding_txo: splice_negotiated.funding_txo, channel_type: splice_negotiated.channel_type, + new_funding_redeem_script: splice_negotiated.funding_redeem_script, }, None, )); @@ -10644,6 +10647,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ user_channel_id: chan.context.get_user_id(), new_funding_txo: splice_negotiated.funding_txo, channel_type: splice_negotiated.channel_type, + new_funding_redeem_script: splice_negotiated.funding_redeem_script, }, None, ));