@@ -6761,20 +6761,32 @@ fn get_legacy_default_holder_max_htlc_value_in_flight_msat(channel_value_satoshi
67616761/// This is used both for outbound and inbound channels and has lower bound
67626762/// of `MIN_THEIR_CHAN_RESERVE_SATOSHIS`, and the `dust_limit_satoshis` of
67636763/// the counterparty.
6764+ ///
6765+ /// Returns `Err` if `channel_value_satoshis` is smaller than
6766+ /// `MIN_THEIR_CHAN_RESERVE_SATOSHIS` or the `dust_limit_satoshis` of the
6767+ /// counterparty.
67646768pub(crate) fn get_holder_selected_channel_reserve_satoshis(
67656769 channel_value_satoshis: u64, their_dust_limit_satoshis: u64, config: &UserConfig,
67666770 is_0reserve: bool,
6767- ) -> u64 {
6771+ ) -> Result<u64, ()> {
6772+ if channel_value_satoshis < MIN_THEIR_CHAN_RESERVE_SATOSHIS
6773+ || channel_value_satoshis < their_dust_limit_satoshis
6774+ {
6775+ return Err(());
6776+ }
67686777 if is_0reserve {
6769- return 0 ;
6778+ return Ok(0) ;
67706779 }
6771- let counterparty_chan_reserve_prop_mil =
6772- config.channel_handshake_config.their_channel_reserve_proportional_millionths as u64;
6780+ // As described in the `ChannelHandshakeConfig` docs, we cap this value at 1_000_000.
6781+ let counterparty_chan_reserve_prop_mil = cmp::min(
6782+ config.channel_handshake_config.their_channel_reserve_proportional_millionths as u64,
6783+ 1_000_000,
6784+ );
67736785 let calculated_reserve =
67746786 channel_value_satoshis.saturating_mul(counterparty_chan_reserve_prop_mil) / 1_000_000;
67756787 let channel_reserve_satoshis = cmp::max(calculated_reserve, MIN_THEIR_CHAN_RESERVE_SATOSHIS);
67766788 let channel_reserve_satoshis = cmp::max(channel_reserve_satoshis, their_dust_limit_satoshis);
6777- cmp::min(channel_value_satoshis, channel_reserve_satoshis)
6789+ Ok( channel_reserve_satoshis)
67786790}
67796791
67806792/// This is for legacy reasons, present for forward-compatibility.
@@ -14479,12 +14491,19 @@ impl<SP: SignerProvider> OutboundV1Channel<SP> {
1447914491 // a dust limit higher than our selected reserve.
1448014492 let their_dust_limit_satoshis = 0;
1448114493 let is_0reserve = trusted_channel_features.is_some_and(|f| f.is_0reserve());
14482- let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(
14483- channel_value_satoshis,
14484- their_dust_limit_satoshis,
14485- config,
14486- is_0reserve,
14487- );
14494+ let holder_selected_channel_reserve_satoshis =
14495+ get_holder_selected_channel_reserve_satoshis(
14496+ channel_value_satoshis,
14497+ their_dust_limit_satoshis,
14498+ config,
14499+ is_0reserve,
14500+ )
14501+ .map_err(|()| APIError::APIMisuseError {
14502+ err: format!(
14503+ "The channel value {channel_value_satoshis} is smaller than \
14504+ {MIN_THEIR_CHAN_RESERVE_SATOSHIS}"
14505+ ),
14506+ })?;
1448814507 if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS && !is_0reserve {
1448914508 // Protocol level safety check in place, although it should never happen because
1449014509 // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` and `MIN_CHANNEL_VALUE_SATOSHIS`
@@ -14876,12 +14895,20 @@ impl<SP: SignerProvider> InboundV1Channel<SP> {
1487614895 let channel_type =
1487714896 channel_type_from_open_channel(&msg.common_fields, our_supported_features)?;
1487814897
14879- let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(
14880- msg.common_fields.funding_satoshis,
14881- msg.common_fields.dust_limit_satoshis,
14882- config,
14883- trusted_channel_features.is_some_and(|f| f.is_0reserve()),
14884- );
14898+ let holder_selected_channel_reserve_satoshis =
14899+ get_holder_selected_channel_reserve_satoshis(
14900+ msg.common_fields.funding_satoshis,
14901+ msg.common_fields.dust_limit_satoshis,
14902+ config,
14903+ trusted_channel_features.is_some_and(|f| f.is_0reserve()),
14904+ )
14905+ .map_err(|()| {
14906+ ChannelError::close(format!(
14907+ "The channel value {} is smaller than either their dust \
14908+ limit {}, or {MIN_THEIR_CHAN_RESERVE_SATOSHIS}",
14909+ msg.common_fields.funding_satoshis, msg.common_fields.dust_limit_satoshis,
14910+ ))
14911+ })?;
1488514912 let counterparty_pubkeys = ChannelPublicKeys {
1488614913 funding_pubkey: msg.common_fields.funding_pubkey,
1488714914 revocation_basepoint: RevocationBasepoint::from(msg.common_fields.revocation_basepoint),
@@ -17483,6 +17510,10 @@ mod tests {
1748317510 // to channel value
1748417511 test_self_and_counterparty_channel_reserve(10_000_000, 0.50, 0.50);
1748517512 test_self_and_counterparty_channel_reserve(10_000_000, 0.60, 0.50);
17513+
17514+ // Make sure we correctly handle reserves greater than the channel value
17515+ test_self_and_counterparty_channel_reserve(100_000, 1.1, 0.30);
17516+ test_self_and_counterparty_channel_reserve(100_000, 0.30, 1.1);
1748617517 }
1748717518
1748817519 #[rustfmt::skip]
@@ -17502,7 +17533,19 @@ mod tests {
1750217533 outbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (outbound_selected_channel_reserve_perc * 1_000_000.0) as u32;
1750317534 let mut chan = OutboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, outbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), channel_value_satoshis, 100_000, 42, &outbound_node_config, 0, 42, None, &logger, None).unwrap();
1750417535
17505- let expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * outbound_selected_channel_reserve_perc) as u64);
17536+ let outbound_capped_reserve_perc = if outbound_selected_channel_reserve_perc.lt(&1.0) {
17537+ outbound_selected_channel_reserve_perc
17538+ } else {
17539+ 1.0
17540+ };
17541+
17542+ let inbound_capped_reserve_perc = if inbound_selected_channel_reserve_perc.lt(&1.0) {
17543+ inbound_selected_channel_reserve_perc
17544+ } else {
17545+ 1.0
17546+ };
17547+
17548+ let expected_outbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * outbound_capped_reserve_perc) as u64);
1750617549 assert_eq!(chan.funding.holder_selected_channel_reserve_satoshis, expected_outbound_selected_chan_reserve);
1750717550
1750817551 let chan_open_channel_msg = chan.get_open_channel(ChainHash::using_genesis_block(network), &&logger).unwrap();
@@ -17512,7 +17555,7 @@ mod tests {
1751217555 if outbound_selected_channel_reserve_perc + inbound_selected_channel_reserve_perc < 1.0 {
1751317556 let chan_inbound_node = InboundV1Channel::<&TestKeysInterface>::new(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, None).unwrap();
1751417557
17515- let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * inbound_selected_channel_reserve_perc ) as u64);
17558+ let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.funding.get_value_satoshis() as f64 * inbound_capped_reserve_perc ) as u64);
1751617559
1751717560 assert_eq!(chan_inbound_node.funding.holder_selected_channel_reserve_satoshis, expected_inbound_selected_chan_reserve);
1751817561 assert_eq!(chan_inbound_node.funding.counterparty_selected_channel_reserve_satoshis.unwrap(), expected_outbound_selected_chan_reserve);
0 commit comments