From 8d31a9be4fddf55df76a805d79ded797a7a09085 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 23 Sep 2025 17:10:16 -0700 Subject: [PATCH] Add UserConfig::reject_inbound_splices This option allows nodes to reject inbound channel splice requests to ensure backwards compatibility is not broken with LDK versions < 0.2 while a splice is pending. Outbound channel splice requests (via `ChannelManager::splice_channel`, an opt-in API) are still allowed as users should be aware of the backwards compatibility risk prior to using the functionality. --- fuzz/src/full_stack.rs | 4 +- lightning/src/ln/channelmanager.rs | 14 ++++-- lightning/src/ln/functional_test_utils.rs | 3 +- lightning/src/ln/splicing_tests.rs | 60 +++++++++++++++++++++++ lightning/src/util/config.rs | 14 ++++++ 5 files changed, 88 insertions(+), 7 deletions(-) diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index ee5f4572eb2..6b00f29b092 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -1048,7 +1048,7 @@ fn two_peer_forwarding_seed() -> Vec { // our network key ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test); // config - ext_from_hex("000000000090000000000000000064000100000000000100ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff0001000000000000", &mut test); + ext_from_hex("000000000090000000000000000064000100000000000100ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000000000", &mut test); // new outbound connection with id 0 ext_from_hex("00", &mut test); @@ -1502,7 +1502,7 @@ fn gossip_exchange_seed() -> Vec { // our network key ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test); // config - ext_from_hex("000000000090000000000000000064000100000000000100ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff0001000000000000", &mut test); + ext_from_hex("000000000090000000000000000064000100000000000100ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000000000", &mut test); // new outbound connection with id 0 ext_from_hex("00", &mut test); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e448802ba5e..5cd5e80069b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -11476,6 +11476,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ counterparty_node_id, msg.channel_id, ), msg.channel_id)), hash_map::Entry::Occupied(mut chan_entry) => { + if self.config.read().unwrap().reject_inbound_splices { + let err = ChannelError::WarnAndDisconnect( + "Inbound channel splices are currently not allowed".to_owned() + ); + return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.channel_id)); + } + if let Some(ref mut funded_channel) = chan_entry.get_mut().as_funded_mut() { let init_res = funded_channel.splice_init( msg, our_funding_contribution, &self.signer_provider, &self.entropy_source, @@ -15325,16 +15332,15 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures { features.set_provide_storage_optional(); #[cfg(simple_close)] features.set_simple_close_optional(); + features.set_quiescence_optional(); + features.set_splicing_optional(); + if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx { features.set_anchors_zero_fee_htlc_tx_optional(); } if config.enable_dual_funded_channels { features.set_dual_fund_optional(); } - // Only signal quiescence support in tests for now, as we don't yet support any - // quiescent-dependent protocols (e.g., splicing). - #[cfg(any(test, fuzzing))] - features.set_quiescence_optional(); if config.channel_handshake_config.negotiate_anchor_zero_fee_commitments { features.set_anchor_zero_fee_commitments_optional(); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index fbfe320d9ea..0bf60e4481d 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1372,7 +1372,7 @@ macro_rules! reload_node { ($node: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => { reload_node!( $node, - $crate::util::config::UserConfig::default(), + test_default_channel_config(), $chanman_encoded, $monitors_encoded, $persister, @@ -4331,6 +4331,7 @@ pub fn test_default_channel_config() -> UserConfig { // feerate of 253). default_config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(50_000_000 / 253); + default_config.reject_inbound_splices = false; default_config } diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 62e1064acc0..ac84eeacaf8 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -507,6 +507,66 @@ fn do_test_splice_state_reset_on_disconnect(reload: bool) { lock_splice_after_blocks(&nodes[0], &nodes[1], channel_id, ANTI_REORG_DELAY - 1); } +#[test] +fn test_config_reject_inbound_splices() { + // Tests that nodes with `reject_inbound_splices` properly reject inbound splices but still + // allow outbound ones. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let mut config = test_default_channel_config(); + config.reject_inbound_splices = true; + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(config)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_id_0 = nodes[0].node.get_our_node_id(); + let node_id_1 = nodes[1].node.get_our_node_id(); + + let (_, _, channel_id, _) = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000); + + let contribution = SpliceContribution::SpliceOut { + outputs: vec![TxOut { + value: Amount::from_sat(1_000), + script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(), + }], + }; + nodes[0] + .node + .splice_channel( + &channel_id, + &node_id_1, + contribution.clone(), + FEERATE_FLOOR_SATS_PER_KW, + None, + ) + .unwrap(); + + let stfu = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1); + nodes[1].node.handle_stfu(node_id_0, &stfu); + let stfu = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0); + nodes[0].node.handle_stfu(node_id_1, &stfu); + + let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1); + nodes[1].node.handle_splice_init(node_id_0, &splice_init); + + let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + if let MessageSendEvent::HandleError { action, .. } = &msg_events[0] { + assert!(matches!(action, msgs::ErrorAction::DisconnectPeerWithWarning { .. })); + } else { + panic!("Expected MessageSendEvent::HandleError"); + } + + nodes[0].node.peer_disconnected(node_id_1); + nodes[1].node.peer_disconnected(node_id_0); + let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]); + reconnect_args.send_channel_ready = (true, true); + reconnect_args.send_announcement_sigs = (true, true); + reconnect_nodes(reconnect_args); + + let _ = splice_channel(&nodes[1], &nodes[0], channel_id, contribution); +} + #[test] fn test_splice_in() { let chanmon_cfgs = create_chanmon_cfgs(2); diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index 079ed19784e..500c0b7c8ae 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -956,6 +956,18 @@ pub struct UserConfig { /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice pub hold_outbound_htlcs_at_next_hop: bool, + /// If this is set to `true`, then inbound channel splice requests will be rejected. This + /// ensures backwards compatibility is not broken with LDK versions < 0.2 while a splice is + /// pending. + /// + /// Outbound channel splice requests (via [`ChannelManager::splice_channel`], an opt-in API) are + /// still allowed as users should be aware of the backwards compatibility risk prior to using + /// the functionality. + /// + /// Default value: `true` + /// + /// [`ChannelManager::splice_channel`]: crate::ln::channelmanager::ChannelManager::splice_channel + pub reject_inbound_splices: bool, } impl Default for UserConfig { @@ -972,6 +984,7 @@ impl Default for UserConfig { enable_dual_funded_channels: false, enable_htlc_hold: false, hold_outbound_htlcs_at_next_hop: false, + reject_inbound_splices: true, } } } @@ -994,6 +1007,7 @@ impl Readable for UserConfig { enable_dual_funded_channels: Readable::read(reader)?, hold_outbound_htlcs_at_next_hop: Readable::read(reader)?, enable_htlc_hold: Readable::read(reader)?, + reject_inbound_splices: Readable::read(reader)?, }) } }