From 656e89e151e5b8e182050591d34c2685407400af 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. Backport of add202ea7f9528794930bffe435f9e077d48a88b --- 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 b633dce574e..52c65fc6b71 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 0075d5a0f19195fcd8b20921b854bfa75df13b06 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 Backport of e3ebe7a8b0c0d5cda54752acb361fb0b737451de --- 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 52c65fc6b71..8cd1e61ab18 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, )); @@ -9623,6 +9625,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, )); @@ -10652,6 +10655,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, ));