Skip to content

Commit 4a3aaef

Browse files
authored
Merge pull request #4484 from tankyleo/2026-03-accept-high-dust-limits
Set max channel dust limit to 10,000 sats for all zero-fee-htlc-tx chans
2 parents a1ff8a0 + 2d5f77b commit 4a3aaef

6 files changed

Lines changed: 52 additions & 22 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -982,8 +982,11 @@ pub const TOTAL_BITCOIN_SUPPLY_SATOSHIS: u64 = 21_000_000 * 1_0000_0000;
982982
/// implementations use this value for their dust limit today.
983983
pub const MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS: u64 = 546;
984984

985+
/// The maximum channel dust limit we will accept from our counterparty for non-anchor channels.
986+
pub const MAX_LEGACY_CHAN_DUST_LIMIT_SATOSHIS: u64 = MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS;
987+
985988
/// The maximum channel dust limit we will accept from our counterparty.
986-
pub const MAX_CHAN_DUST_LIMIT_SATOSHIS: u64 = MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS;
989+
pub const MAX_CHAN_DUST_LIMIT_SATOSHIS: u64 = 10_000;
987990

988991
/// The dust limit is used for both the commitment transaction outputs as well as the closing
989992
/// transactions. For cooperative closing transactions, we require segwit outputs, though accept
@@ -3644,8 +3647,14 @@ impl<SP: SignerProvider> ChannelContext<SP> {
36443647
if open_channel_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS {
36453648
return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS)));
36463649
}
3647-
if open_channel_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS {
3648-
return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS)));
3650+
3651+
let max_chan_dust_limit_satoshis = if channel_type.supports_anchors_zero_fee_htlc_tx() || channel_type.supports_anchor_zero_fee_commitments() {
3652+
MAX_CHAN_DUST_LIMIT_SATOSHIS
3653+
} else {
3654+
MAX_LEGACY_CHAN_DUST_LIMIT_SATOSHIS
3655+
};
3656+
if open_channel_fields.dust_limit_satoshis > max_chan_dust_limit_satoshis {
3657+
return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, max_chan_dust_limit_satoshis)));
36493658
}
36503659

36513660
// Convert things into internal flags and prep our state:
@@ -4426,8 +4435,14 @@ impl<SP: SignerProvider> ChannelContext<SP> {
44264435
if common_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS {
44274436
return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", common_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS)));
44284437
}
4429-
if common_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS {
4430-
return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", common_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS)));
4438+
4439+
let max_chan_dust_limit_satoshis = if channel_type.supports_anchors_zero_fee_htlc_tx() || channel_type.supports_anchor_zero_fee_commitments() {
4440+
MAX_CHAN_DUST_LIMIT_SATOSHIS
4441+
} else {
4442+
MAX_LEGACY_CHAN_DUST_LIMIT_SATOSHIS
4443+
};
4444+
if common_fields.dust_limit_satoshis > max_chan_dust_limit_satoshis {
4445+
return Err(ChannelError::close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", common_fields.dust_limit_satoshis, max_chan_dust_limit_satoshis)));
44314446
}
44324447
if common_fields.minimum_depth > peer_limits.max_minimum_depth {
44334448
return Err(ChannelError::close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, common_fields.minimum_depth)));
@@ -6280,15 +6295,18 @@ fn get_holder_max_htlc_value_in_flight_msat(
62806295
/// Guaranteed to return a value no larger than channel_value_satoshis
62816296
///
62826297
/// This is used both for outbound and inbound channels and has lower bound
6283-
/// of `MIN_THEIR_CHAN_RESERVE_SATOSHIS`.
6298+
/// of `MIN_THEIR_CHAN_RESERVE_SATOSHIS`, and the `dust_limit_satoshis` of
6299+
/// the counterparty.
62846300
pub(crate) fn get_holder_selected_channel_reserve_satoshis(
6285-
channel_value_satoshis: u64, config: &UserConfig,
6301+
channel_value_satoshis: u64, their_dust_limit_satoshis: u64, config: &UserConfig,
62866302
) -> u64 {
62876303
let counterparty_chan_reserve_prop_mil =
62886304
config.channel_handshake_config.their_channel_reserve_proportional_millionths as u64;
62896305
let calculated_reserve =
62906306
channel_value_satoshis.saturating_mul(counterparty_chan_reserve_prop_mil) / 1_000_000;
6291-
cmp::min(channel_value_satoshis, cmp::max(calculated_reserve, MIN_THEIR_CHAN_RESERVE_SATOSHIS))
6307+
let channel_reserve_satoshis = cmp::max(calculated_reserve, MIN_THEIR_CHAN_RESERVE_SATOSHIS);
6308+
let channel_reserve_satoshis = cmp::max(channel_reserve_satoshis, their_dust_limit_satoshis);
6309+
cmp::min(channel_value_satoshis, channel_reserve_satoshis)
62926310
}
62936311

62946312
/// This is for legacy reasons, present for forward-compatibility.
@@ -13267,7 +13285,15 @@ impl<SP: SignerProvider> OutboundV1Channel<SP> {
1326713285
channel_value_satoshis: u64, push_msat: u64, user_id: u128, config: &UserConfig, current_chain_height: u32,
1326813286
outbound_scid_alias: u64, temporary_channel_id: Option<ChannelId>, logger: L
1326913287
) -> Result<OutboundV1Channel<SP>, APIError> {
13270-
let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config);
13288+
// At this point, we do not know what `dust_limit_satoshis` the counterparty will want for themselves,
13289+
// so we set the channel reserve with no regard for their dust limit, and fail the channel if they want
13290+
// a dust limit higher than our selected reserve.
13291+
let their_dust_limit_satoshis = 0;
13292+
let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(
13293+
channel_value_satoshis,
13294+
their_dust_limit_satoshis,
13295+
config
13296+
);
1327113297
if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS {
1327213298
// Protocol level safety check in place, although it should never happen because
1327313299
// of `MIN_THEIR_CHAN_RESERVE_SATOSHIS`
@@ -13649,7 +13675,11 @@ impl<SP: SignerProvider> InboundV1Channel<SP> {
1364913675
// support this channel type.
1365013676
let channel_type = channel_type_from_open_channel(&msg.common_fields, our_supported_features)?;
1365113677

13652-
let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config);
13678+
let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(
13679+
msg.common_fields.funding_satoshis,
13680+
msg.common_fields.dust_limit_satoshis,
13681+
config
13682+
);
1365313683
let counterparty_pubkeys = ChannelPublicKeys {
1365413684
funding_pubkey: msg.common_fields.funding_pubkey,
1365513685
revocation_basepoint: RevocationBasepoint::from(msg.common_fields.revocation_basepoint),

lightning/src/ln/channel_open_tests.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ pub fn test_insane_channel_opens() {
470470
// funding satoshis
471471
let channel_value_sat = 31337; // same as funding satoshis
472472
let channel_reserve_satoshis =
473-
get_holder_selected_channel_reserve_satoshis(channel_value_sat, &legacy_cfg);
473+
get_holder_selected_channel_reserve_satoshis(channel_value_sat, 0, &legacy_cfg);
474474
let push_msat = (channel_value_sat - channel_reserve_satoshis) * 1000;
475475

476476
// Have node0 initiate a channel to node1 with aforementioned parameters
@@ -880,8 +880,7 @@ pub fn bolt2_open_channel_sane_dust_limit() {
880880
nodes[0].node.create_channel(node_b_id, value_sats, push_msat, 42, None, None).unwrap();
881881
let mut node0_to_1_send_open_channel =
882882
get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, node_b_id);
883-
node0_to_1_send_open_channel.common_fields.dust_limit_satoshis = 547;
884-
node0_to_1_send_open_channel.channel_reserve_satoshis = 100001;
883+
node0_to_1_send_open_channel.common_fields.dust_limit_satoshis = 10_001;
885884

886885
nodes[1].node.handle_open_channel(node_a_id, &node0_to_1_send_open_channel);
887886
let events = nodes[1].node.get_and_clear_pending_events();
@@ -893,7 +892,7 @@ pub fn bolt2_open_channel_sane_dust_limit() {
893892
{
894893
Err(APIError::ChannelUnavailable { err }) => assert_eq!(
895894
err,
896-
"dust_limit_satoshis (547) is greater than the implementation limit (546)"
895+
"dust_limit_satoshis (10001) is greater than the implementation limit (10000)"
897896
),
898897
_ => panic!(),
899898
},

lightning/src/ln/functional_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ pub fn test_inbound_outbound_capacity_is_not_zero() {
413413
assert_eq!(channels0.len(), 1);
414414
assert_eq!(channels1.len(), 1);
415415

416-
let reserve = get_holder_selected_channel_reserve_satoshis(100_000, &default_config);
416+
let reserve = get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config);
417417
assert_eq!(channels0[0].inbound_capacity_msat, 95000000 - reserve * 1000);
418418
assert_eq!(channels1[0].outbound_capacity_msat, 95000000 - reserve * 1000);
419419

lightning/src/ln/htlc_reserve_unit_tests.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) {
5050
push_amt -= feerate_per_kw as u64
5151
* (commitment_tx_base_weight(&channel_type_features) + 4 * COMMITMENT_TX_WEIGHT_PER_HTLC)
5252
/ 1000 * 1000;
53-
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
53+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config) * 1000;
5454

5555
let push = if send_from_initiator { 0 } else { push_amt };
5656
let temp_channel_id =
@@ -1008,7 +1008,7 @@ pub fn test_chan_reserve_violation_outbound_htlc_inbound_chan() {
10081008
&channel_type_features,
10091009
);
10101010

1011-
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
1011+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config) * 1000;
10121012

10131013
let _ = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, push_amt);
10141014

@@ -1052,7 +1052,7 @@ pub fn test_chan_reserve_violation_inbound_htlc_outbound_channel() {
10521052
MIN_AFFORDABLE_HTLC_COUNT as u64,
10531053
&channel_type_features,
10541054
);
1055-
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
1055+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config) * 1000;
10561056
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, push_amt);
10571057

10581058
// Send four HTLCs to cover the initial push_msat buffer we're required to include
@@ -1130,7 +1130,7 @@ pub fn test_chan_reserve_dust_inbound_htlcs_outbound_chan() {
11301130
MIN_AFFORDABLE_HTLC_COUNT as u64,
11311131
&channel_type_features,
11321132
);
1133-
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, &default_config) * 1000;
1133+
push_amt -= get_holder_selected_channel_reserve_satoshis(100_000, 0, &default_config) * 1000;
11341134
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, push_amt);
11351135

11361136
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
@@ -4985,7 +4985,7 @@ fn test_htlc_forward_considers_anchor_outputs_value() {
49854985
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, CHAN_AMT, PUSH_MSAT);
49864986

49874987
let channel_reserve_msat =
4988-
get_holder_selected_channel_reserve_satoshis(CHAN_AMT, &config) * 1000;
4988+
get_holder_selected_channel_reserve_satoshis(CHAN_AMT, 0, &config) * 1000;
49894989
let commitment_fee_msat = chan_utils::commit_tx_fee_sat(
49904990
*nodes[1].fee_estimator.sat_per_kw.lock().unwrap(),
49914991
2,

lightning/src/ln/update_fee_tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,8 @@ pub fn do_test_update_fee_that_funder_cannot_afford(channel_type_features: Chann
408408
);
409409
let channel_id = chan.2;
410410
let secp_ctx = Secp256k1::new();
411-
let bs_channel_reserve_sats = get_holder_selected_channel_reserve_satoshis(channel_value, &cfg);
411+
let bs_channel_reserve_sats =
412+
get_holder_selected_channel_reserve_satoshis(channel_value, 0, &cfg);
412413
let (anchor_outputs_value_sats, outputs_num_no_htlcs) =
413414
if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
414415
(ANCHOR_OUTPUT_VALUE_SATOSHI * 2, 4)
@@ -892,7 +893,7 @@ pub fn test_chan_init_feerate_unaffordability() {
892893

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

0 commit comments

Comments
 (0)