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` ///