From 6c2090d373e1fa391a0c2a5c1a5afde7c2168301 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 15 May 2026 17:17:43 +0000 Subject: [PATCH] Enforce `min_funding_satoshis` after splices In inbound channels, we already enforce this minimum at channel open, so it makes sense to also enforce this minimum on any splices in which the counterparty's contribution is negative. Codex wrote the tests. --- lightning/src/ln/channel.rs | 54 ++++-- lightning/src/ln/channelmanager.rs | 4 + lightning/src/ln/splicing_tests.rs | 291 +++++++++++++++++++++++++++++ lightning/src/util/config.rs | 3 +- 4 files changed, 339 insertions(+), 13 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 58dd6ea30c0..fde56a9adcb 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2778,7 +2778,7 @@ impl FundingScope { fn for_splice( prev_funding: &Self, context: &ChannelContext, our_funding_contribution: SignedAmount, their_funding_contribution: SignedAmount, counterparty_funding_pubkey: PublicKey, - our_new_holder_keys: ChannelPublicKeys, + our_new_holder_keys: ChannelPublicKeys, min_funding_satoshis: u64, ) -> Result { if our_funding_contribution.unsigned_abs() > Amount::MAX_MONEY { return Err(format!( @@ -2817,13 +2817,27 @@ impl FundingScope { ), )?; - let post_channel_value_sat = prev_funding.get_value_satoshis() + let post_channel_value_sat = prev_funding + .get_value_satoshis() .checked_add_signed(our_funding_contribution.to_sat()) .and_then(|v| v.checked_add_signed(their_funding_contribution.to_sat())) - .ok_or(format!("The sum of contributions {our_funding_contribution} and {their_funding_contribution} is greater than the channel's value"))?; + .ok_or(format!( + "The sum of contributions {our_funding_contribution} and \ + {their_funding_contribution} is greater than the channel's value" + ))?; if post_channel_value_sat < MIN_CHANNEL_VALUE_SATOSHIS { return Err(format!( - "Spliced channel value must be at least 1000 satoshis. It would be {post_channel_value_sat}", + "Spliced channel value must be at least 1000 satoshis. It would be \ + {post_channel_value_sat}" + )); + } + if post_channel_value_sat < min_funding_satoshis + && their_funding_contribution.is_negative() + && !prev_funding.is_outbound() + { + return Err(format!( + "Spliced channel value {post_channel_value_sat} would be smaller \ + than the configured min_funding_satoshis {min_funding_satoshis}" )); } @@ -13048,6 +13062,7 @@ where fn validate_splice_contributions( &self, our_funding_contribution: SignedAmount, their_funding_contribution: SignedAmount, counterparty_funding_pubkey: PublicKey, our_new_holder_keys: ChannelPublicKeys, + min_funding_satoshis: u64, ) -> Result { let candidate_scope = FundingScope::for_splice( &self.funding, @@ -13056,6 +13071,7 @@ where their_funding_contribution, counterparty_funding_pubkey, our_new_holder_keys, + min_funding_satoshis, ) .map_err(|e| format!("Channel {} cannot be spliced; {}", self.context.channel_id(), e))?; @@ -13166,7 +13182,7 @@ where pub(crate) fn splice_init( &mut self, msg: &msgs::SpliceInit, entropy_source: &ES, holder_node_id: &PublicKey, - logger: &L, + min_funding_satoshis: u64, logger: &L, ) -> Result { self.validate_splice_init(msg).map_err(|e| self.quiescent_negotiation_err(e))?; @@ -13199,6 +13215,7 @@ where their_funding_contribution, msg.funding_pubkey, holder_pubkeys, + min_funding_satoshis, ) .map_err(|e| self.quiescent_negotiation_err(ChannelError::WarnAndDisconnect(e)))?; @@ -13334,7 +13351,7 @@ where pub(crate) fn tx_init_rbf( &mut self, msg: &msgs::TxInitRbf, entropy_source: &ES, holder_node_id: &PublicKey, - fee_estimator: &LowerBoundedFeeEstimator, logger: &L, + fee_estimator: &LowerBoundedFeeEstimator, min_funding_satoshis: u64, logger: &L, ) -> Result { let (holder_pubkeys, counterparty_funding_pubkey) = self .validate_tx_init_rbf(msg, fee_estimator) @@ -13381,6 +13398,7 @@ where their_funding_contribution, counterparty_funding_pubkey, holder_pubkeys, + min_funding_satoshis, ) .map_err(|e| self.quiescent_negotiation_err(ChannelError::WarnAndDisconnect(e)))?; @@ -13452,7 +13470,9 @@ where }) } - fn validate_tx_ack_rbf(&self, msg: &msgs::TxAckRbf) -> Result { + fn validate_tx_ack_rbf( + &self, msg: &msgs::TxAckRbf, min_funding_satoshis: u64, + ) -> Result { let pending_splice = self .pending_splice .as_ref() @@ -13478,6 +13498,7 @@ where their_funding_contribution, counterparty_funding_pubkey, holder_pubkeys, + min_funding_satoshis, ) .map_err(|e| ChannelError::WarnAndDisconnect(e))?; @@ -13486,9 +13507,9 @@ where pub(crate) fn tx_ack_rbf( &mut self, msg: &msgs::TxAckRbf, entropy_source: &ES, holder_node_id: &PublicKey, - logger: &L, + min_funding_satoshis: u64, logger: &L, ) -> Result, ChannelError> { - let rbf_funding = self.validate_tx_ack_rbf(msg)?; + let rbf_funding = self.validate_tx_ack_rbf(msg, min_funding_satoshis)?; log_info!( logger, @@ -13519,9 +13540,9 @@ where pub(crate) fn splice_ack( &mut self, msg: &msgs::SpliceAck, entropy_source: &ES, holder_node_id: &PublicKey, - logger: &L, + min_funding_satoshis: u64, logger: &L, ) -> Result, ChannelError> { - let splice_funding = self.validate_splice_ack(msg)?; + let splice_funding = self.validate_splice_ack(msg, min_funding_satoshis)?; log_info!( logger, @@ -13553,7 +13574,9 @@ where Ok(tx_msg_opt) } - fn validate_splice_ack(&self, msg: &msgs::SpliceAck) -> Result { + fn validate_splice_ack( + &self, msg: &msgs::SpliceAck, min_funding_satoshis: u64, + ) -> Result { // TODO(splicing): Add check that we are the splice (quiescence) initiator let pending_splice = self @@ -13576,6 +13599,7 @@ where their_funding_contribution, msg.funding_pubkey, new_keys, + min_funding_satoshis, ) .map_err(|e| ChannelError::WarnAndDisconnect(e))?; @@ -13692,6 +13716,9 @@ where SignedAmount::ZERO, funding.counterparty_funding_pubkey().clone(), funding.get_holder_pubkeys().clone(), + // When the counterparty's contribution is non-negative, we don't validate + // the post splice channel value against `min_funding_satoshis` + 0, ) .unwrap(); // Splice-out an additional satoshi, and validation fails! @@ -13700,6 +13727,9 @@ where SignedAmount::ZERO, funding.counterparty_funding_pubkey().clone(), funding.get_holder_pubkeys().clone(), + // When the counterparty's contribution is non-negative, we don't validate + // the post splice channel value against `min_funding_satoshis` + 0, ) .unwrap_err(); } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 19fd2f96797..35754147b0e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -13384,6 +13384,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ msg, &self.entropy_source, &self.get_our_node_id(), + self.config.read().unwrap().channel_handshake_limits.min_funding_satoshis, &self.logger, ) { Ok(splice_ack_msg) => { @@ -13441,6 +13442,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ &self.entropy_source, &self.get_our_node_id(), &self.fee_estimator, + self.config.read().unwrap().channel_handshake_limits.min_funding_satoshis, &self.logger, ) { Ok(tx_ack_rbf_msg) => { @@ -13497,6 +13499,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ msg, &self.entropy_source, &self.get_our_node_id(), + self.config.read().unwrap().channel_handshake_limits.min_funding_satoshis, &self.logger, ); let tx_msg_opt = @@ -13541,6 +13544,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ msg, &self.entropy_source, &self.get_our_node_id(), + self.config.read().unwrap().channel_handshake_limits.min_funding_satoshis, &self.logger, ); let tx_msg_opt = diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs index 6bd5d5224f7..a6823e39aed 100644 --- a/lightning/src/ln/splicing_tests.rs +++ b/lightning/src/ln/splicing_tests.rs @@ -163,6 +163,35 @@ impl CoinSelectionSourceSync for TightBudgetWallet { } } +#[cfg(test)] +fn config_with_min_funding_satoshis(min_funding_satoshis: u64) -> UserConfig { + let mut config = test_default_channel_config(); + config.channel_handshake_limits.min_funding_satoshis = min_funding_satoshis; + config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage = + 100; + config +} + +#[cfg(test)] +fn assert_min_funding_error<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, min_funding_satoshis: u64) { + let msg_events = node.node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1, "{msg_events:?}"); + match &msg_events[0] { + MessageSendEvent::HandleError { + action: msgs::ErrorAction::DisconnectPeerWithWarning { msg }, + .. + } => { + assert!( + msg.data + .contains(&format!("configured min_funding_satoshis {min_funding_satoshis}")), + "unexpected warning: {}", + msg.data + ); + }, + _ => panic!("Expected HandleError with warning, got {:?}", msg_events[0]), + } +} + pub fn negotiate_splice_tx<'a, 'b, 'c, 'd>( initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId, funding_contribution: FundingContribution, @@ -1209,6 +1238,268 @@ fn test_splice_in() { let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat); } +#[test] +fn test_min_funding_satoshis_allows_splice_init_with_positive_counterparty_contribution() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + 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 min_funding_satoshis = 150_000; + let initial_channel_value_sat = 100_000; + let (_, _, channel_id, _) = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0); + nodes[1].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis)); + + let added_value = Amount::from_sat(10_000); + provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000)); + let _funding_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value); + + let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1); + nodes[1].node.handle_stfu(node_id_0, &stfu_init); + let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0); + nodes[0].node.handle_stfu(node_id_1, &stfu_ack); + + let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1); + assert!(splice_init.funding_contribution_satoshis > 0); + nodes[1].node.handle_splice_init(node_id_0, &splice_init); + let _splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0); +} + +#[test] +fn test_min_funding_satoshis_rejects_splice_init_with_negative_counterparty_contribution() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + 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 min_funding_satoshis = 150_000; + let initial_channel_value_sat = 100_000; + let (_, _, channel_id, _) = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0); + nodes[1].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis)); + + let outputs = vec![TxOut { + value: Amount::from_sat(10_000), + script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(), + }]; + let _funding_contribution = + initiate_splice_out(&nodes[0], &nodes[1], channel_id, outputs).unwrap(); + + let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1); + nodes[1].node.handle_stfu(node_id_0, &stfu_init); + let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0); + nodes[0].node.handle_stfu(node_id_1, &stfu_ack); + + let splice_init = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceInit, node_id_1); + assert!(splice_init.funding_contribution_satoshis < 0); + nodes[1].node.handle_splice_init(node_id_0, &splice_init); + assert_min_funding_error(&nodes[1], min_funding_satoshis); +} + +#[test] +fn test_min_funding_satoshis_allows_outbound_splice_ack_with_negative_counterparty_contribution() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + 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 min_funding_satoshis = 150_000; + let initial_channel_value_sat = 100_000; + let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value( + &nodes, + 0, + 1, + initial_channel_value_sat, + 50_000_000, + ); + nodes[0].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis)); + + let added_value = Amount::from_sat(10_000); + provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000)); + let _node_0_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value); + let outputs = vec![TxOut { + value: Amount::from_sat(10_000), + script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(), + }]; + let _node_1_contribution = + initiate_splice_out(&nodes[1], &nodes[0], channel_id, outputs).unwrap(); + + let stfu_0 = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1); + let stfu_1 = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0); + nodes[1].node.handle_stfu(node_id_0, &stfu_0); + assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + nodes[0].node.handle_stfu(node_id_1, &stfu_1); + + 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 splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0); + assert!(splice_ack.funding_contribution_satoshis < 0); + nodes[0].node.handle_splice_ack(node_id_1, &splice_ack); + + let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert!(!msg_events.is_empty(), "{msg_events:?}"); + assert!( + !msg_events.iter().any(|event| matches!(event, MessageSendEvent::HandleError { .. })), + "{msg_events:?}" + ); +} + +#[test] +fn test_min_funding_satoshis_rejects_splice_ack_with_negative_counterparty_contribution() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + 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 min_funding_satoshis = 150_000; + let initial_channel_value_sat = 100_000; + let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value( + &nodes, + 1, + 0, + initial_channel_value_sat, + 50_000_000, + ); + nodes[0].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis)); + + let added_value = Amount::from_sat(10_000); + provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000)); + let _node_0_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value); + + let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1); + nodes[1].node.handle_stfu(node_id_0, &stfu_init); + + let outputs = vec![TxOut { + value: Amount::from_sat(10_000), + script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(), + }]; + let _node_1_contribution = + initiate_splice_out(&nodes[1], &nodes[0], channel_id, outputs).unwrap(); + + let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0); + assert!(!stfu_ack.initiator); + nodes[0].node.handle_stfu(node_id_1, &stfu_ack); + + 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 splice_ack = get_event_msg!(nodes[1], MessageSendEvent::SendSpliceAck, node_id_0); + assert!(splice_ack.funding_contribution_satoshis < 0); + nodes[0].node.handle_splice_ack(node_id_1, &splice_ack); + assert_min_funding_error(&nodes[0], min_funding_satoshis); +} + +#[test] +fn test_min_funding_satoshis_rejects_tx_init_rbf_with_negative_counterparty_contribution() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + 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 min_funding_satoshis = 150_000; + let initial_channel_value_sat = 100_000; + let (_, _, channel_id, _) = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0); + nodes[1].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis)); + + let added_value = Amount::from_sat(10_000); + provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000)); + let first_contribution = initiate_splice_in(&nodes[1], &nodes[0], channel_id, added_value); + let (_first_splice_tx, _) = + splice_channel(&nodes[1], &nodes[0], channel_id, first_contribution); + + let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap(); + let rbf_feerate = funding_template.min_rbf_feerate().unwrap(); + let outputs = vec![TxOut { + value: Amount::from_sat(10_000), + script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(), + }]; + let rbf_contribution = funding_template.splice_out(outputs, rbf_feerate, FeeRate::MAX).unwrap(); + nodes[0].node.funding_contributed(&channel_id, &node_id_1, rbf_contribution, None).unwrap(); + + let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1); + nodes[1].node.handle_stfu(node_id_0, &stfu_init); + let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0); + nodes[0].node.handle_stfu(node_id_1, &stfu_ack); + + let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1); + assert!(tx_init_rbf.funding_output_contribution.unwrap() < 0); + nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf); + assert_min_funding_error(&nodes[1], min_funding_satoshis); +} + +#[test] +fn test_min_funding_satoshis_rejects_tx_ack_rbf_with_negative_counterparty_contribution() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + 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 min_funding_satoshis = 150_000; + let initial_channel_value_sat = 100_000; + let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value( + &nodes, + 1, + 0, + initial_channel_value_sat, + 50_000_000, + ); + nodes[0].node.set_current_config(config_with_min_funding_satoshis(min_funding_satoshis)); + + let added_value = Amount::from_sat(10_000); + provide_utxo_reserves(&nodes, 1, Amount::from_sat(100_000)); + let first_contribution = initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value); + let (_first_splice_tx, _) = + splice_channel(&nodes[0], &nodes[1], channel_id, first_contribution); + + let funding_template_0 = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap(); + let rbf_feerate = funding_template_0.min_rbf_feerate().unwrap(); + let node_0_contribution = + funding_template_0.with_prior_contribution(rbf_feerate, FeeRate::MAX).build().unwrap(); + nodes[0].node.funding_contributed(&channel_id, &node_id_1, node_0_contribution, None).unwrap(); + + let stfu_init = get_event_msg!(nodes[0], MessageSendEvent::SendStfu, node_id_1); + nodes[1].node.handle_stfu(node_id_0, &stfu_init); + + let outputs = vec![TxOut { + value: Amount::from_sat(10_000), + script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(), + }]; + let funding_template_1 = nodes[1].node.splice_channel(&channel_id, &node_id_0).unwrap(); + let node_1_contribution = + funding_template_1.splice_out(outputs, rbf_feerate, FeeRate::MAX).unwrap(); + nodes[1].node.funding_contributed(&channel_id, &node_id_0, node_1_contribution, None).unwrap(); + + let stfu_ack = get_event_msg!(nodes[1], MessageSendEvent::SendStfu, node_id_0); + assert!(!stfu_ack.initiator); + nodes[0].node.handle_stfu(node_id_1, &stfu_ack); + + let tx_init_rbf = get_event_msg!(nodes[0], MessageSendEvent::SendTxInitRbf, node_id_1); + nodes[1].node.handle_tx_init_rbf(node_id_0, &tx_init_rbf); + let tx_ack_rbf = get_event_msg!(nodes[1], MessageSendEvent::SendTxAckRbf, node_id_0); + assert!(tx_ack_rbf.funding_output_contribution.unwrap() < 0); + nodes[0].node.handle_tx_ack_rbf(node_id_1, &tx_ack_rbf); + assert_min_funding_error(&nodes[0], min_funding_satoshis); +} + #[test] fn test_splice_out() { let chanmon_cfgs = create_chanmon_cfgs(2); diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index 78ab45d58c2..fa01f8e21b4 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -323,7 +323,8 @@ impl Readable for ChannelHandshakeConfig { #[derive(Copy, Clone, Debug)] pub struct ChannelHandshakeLimits { /// Minimum allowed satoshis when a channel is funded. This is supplied by the sender and so - /// only applies to inbound channels. + /// only applies to inbound channels. It is also enforced for inbound channels on splices in + /// which the counterparty's contribution is negative. /// /// Default value: `1000` ///