Skip to content

Commit 53e156a

Browse files
committed
Error if the calculated v1 reserve is greater than the channel value
We made the same change to the calculation of the v2 reserve in the previous commit.
1 parent 3835f84 commit 53e156a

6 files changed

Lines changed: 89 additions & 33 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
67646768
pub(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);

lightning/src/ln/channel_open_tests.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::chain::{self, ChannelMonitorUpdateStatus};
1616
use crate::events::{ClosureReason, Event, FundingInfo};
1717
use crate::ln::channel::{
1818
get_holder_selected_channel_reserve_satoshis, ChannelError, InboundV1Channel,
19-
OutboundV1Channel, COINBASE_MATURITY, UNFUNDED_CHANNEL_AGE_LIMIT_TICKS,
19+
OutboundV1Channel, COINBASE_MATURITY, MIN_THEIR_CHAN_RESERVE_SATOSHIS,
20+
UNFUNDED_CHANNEL_AGE_LIMIT_TICKS,
2021
};
2122
use crate::ln::channelmanager::{
2223
self, TrustedChannelFeatures, BREAKDOWN_TIMEOUT, MAX_UNFUNDED_CHANNEL_PEERS,
@@ -473,7 +474,8 @@ pub fn test_insane_channel_opens() {
473474
// funding satoshis
474475
let channel_value_sat = 31337; // same as funding satoshis
475476
let channel_reserve_satoshis =
476-
get_holder_selected_channel_reserve_satoshis(channel_value_sat, 0, &legacy_cfg, false);
477+
get_holder_selected_channel_reserve_satoshis(channel_value_sat, 0, &legacy_cfg, false)
478+
.unwrap();
477479
let push_msat = (channel_value_sat - channel_reserve_satoshis) * 1000;
478480

479481
// Have node0 initiate a channel to node1 with aforementioned parameters
@@ -552,7 +554,13 @@ pub fn test_insane_channel_opens() {
552554
},
553555
);
554556

555-
insane_open_helper("Peer never wants payout outputs?", |mut msg| {
557+
let crazy_dust_limit = channel_value_sat + 1;
558+
let expected_error_str = format!(
559+
"Got non-closing error: The channel value \
560+
{channel_value_sat} is smaller than either their dust limit {crazy_dust_limit}, or \
561+
{MIN_THEIR_CHAN_RESERVE_SATOSHIS}"
562+
);
563+
insane_open_helper(&expected_error_str, |mut msg| {
556564
msg.common_fields.dust_limit_satoshis = msg.common_fields.funding_satoshis + 1;
557565
msg
558566
});

lightning/src/ln/functional_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,8 @@ pub fn test_inbound_outbound_capacity_is_not_zero() {
415415
assert_eq!(channels0.len(), 1);
416416
assert_eq!(channels1.len(), 1);
417417

418-
let reserve = get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false);
418+
let reserve =
419+
get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false).unwrap();
419420
assert_eq!(channels0[0].inbound_capacity_msat, 95000000 - reserve * 1000);
420421
assert_eq!(channels1[0].outbound_capacity_msat, 95000000 - reserve * 1000);
421422

lightning/src/ln/htlc_reserve_unit_tests.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) {
5555
push_amt -= feerate_per_kw as u64
5656
* (commitment_tx_base_weight(&channel_type_features) + 4 * COMMITMENT_TX_WEIGHT_PER_HTLC)
5757
/ 1000 * 1000;
58-
push_amt -=
59-
get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false) * 1000;
58+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false)
59+
.unwrap()
60+
* 1000;
6061

6162
let push = if send_from_initiator { 0 } else { push_amt };
6263
let temp_channel_id =
@@ -1002,8 +1003,9 @@ pub fn test_chan_reserve_violation_outbound_htlc_inbound_chan() {
10021003
&channel_type_features,
10031004
);
10041005

1005-
push_amt -=
1006-
get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false) * 1000;
1006+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false)
1007+
.unwrap()
1008+
* 1000;
10071009

10081010
let _ = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, push_amt);
10091011

@@ -1048,8 +1050,9 @@ pub fn test_chan_reserve_dust_inbound_htlcs_outbound_chan() {
10481050
MIN_AFFORDABLE_HTLC_COUNT as u64,
10491051
&channel_type_features,
10501052
);
1051-
push_amt -=
1052-
get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false) * 1000;
1053+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false)
1054+
.unwrap()
1055+
* 1000;
10531056
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, push_amt);
10541057

10551058
let (htlc_success_tx_fee_sat, _) =

lightning/src/ln/payment_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5043,7 +5043,7 @@ fn test_htlc_forward_considers_anchor_outputs_value() {
50435043
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, CHAN_AMT, PUSH_MSAT);
50445044

50455045
let channel_reserve_msat =
5046-
get_holder_selected_channel_reserve_satoshis(CHAN_AMT, 0, &config, false) * 1000;
5046+
get_holder_selected_channel_reserve_satoshis(CHAN_AMT, 0, &config, false).unwrap() * 1000;
50475047
let commitment_fee_msat = chan_utils::commit_tx_fee_sat(
50485048
*nodes[1].fee_estimator.sat_per_kw.lock().unwrap(),
50495049
2,

lightning/src/ln/update_fee_tests.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ pub fn do_test_update_fee_that_funder_cannot_afford(channel_type_features: Chann
410410
let channel_id = chan.2;
411411
let secp_ctx = Secp256k1::new();
412412
let bs_channel_reserve_sats =
413-
get_holder_selected_channel_reserve_satoshis(channel_value, 0, &cfg, false);
413+
get_holder_selected_channel_reserve_satoshis(channel_value, 0, &cfg, false).unwrap();
414414
let (anchor_outputs_value_sats, outputs_num_no_htlcs) =
415415
if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
416416
(ANCHOR_OUTPUT_VALUE_SATOSHI * 2, 4)
@@ -886,8 +886,9 @@ pub fn test_chan_init_feerate_unaffordability() {
886886

887887
// During open, we don't have a "counterparty channel reserve" to check against, so that
888888
// requirement only comes into play on the open_channel handling side.
889-
push_amt -=
890-
get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false) * 1000;
889+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config, false)
890+
.unwrap()
891+
* 1000;
891892
nodes[0].node.create_channel(node_b_id, 100_000, push_amt, 42, None, None).unwrap();
892893
let mut open_channel_msg =
893894
get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id);

0 commit comments

Comments
 (0)