Skip to content

Commit 6eb5bf2

Browse files
authored
Merge pull request #4625 from tankyleo/2026-05-check-both-commitments
Validate `next_splice_out_maximum_sat` and reserved fees on both commitments
2 parents 9ce02b3 + 9fe1a36 commit 6eb5bf2

4 files changed

Lines changed: 720 additions & 182 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,6 +2553,9 @@ pub(super) struct FundingScope {
25532553
value_to_self_msat: u64, // Excluding all pending_htlcs, fees, and anchor outputs
25542554

25552555
/// minimum channel reserve for self to maintain - set by them.
2556+
#[cfg(any(test, feature = "_externalize_tests"))]
2557+
pub(super) counterparty_selected_channel_reserve_satoshis: Option<u64>,
2558+
#[cfg(not(any(test, feature = "_externalize_tests")))]
25562559
counterparty_selected_channel_reserve_satoshis: Option<u64>,
25572560

25582561
#[cfg(any(test, feature = "_externalize_tests"))]

lightning/src/ln/htlc_reserve_unit_tests.rs

Lines changed: 267 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,10 +1053,10 @@ pub fn test_chan_reserve_dust_inbound_htlcs_outbound_chan() {
10531053
* 1000;
10541054
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, push_amt);
10551055

1056-
let (htlc_success_tx_fee_sat, _) =
1056+
let (_htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) =
10571057
second_stage_tx_fees_sat(&channel_type_features, feerate_per_kw);
10581058
let dust_amt = crate::ln::channel::MIN_CHAN_DUST_LIMIT_SATOSHIS * 1000
1059-
+ htlc_success_tx_fee_sat * 1000
1059+
+ htlc_timeout_tx_fee_sat * 1000
10601060
- 1;
10611061
// In the previous code, routing this dust payment would cause nodes[0] to perceive a channel
10621062
// reserve violation even though it's a dust HTLC and therefore shouldn't count towards the
@@ -3528,3 +3528,268 @@ fn test_fail_cannot_afford_dust_htlcs_at_spike_multiple_if_nondust_at_base_feera
35283528
true,
35293529
);
35303530
}
3531+
3532+
#[xtest(feature = "_externalize_tests")]
3533+
fn test_available_balances_both_commitments_dust_on_funder_commitment() {
3534+
let mut config = test_default_channel_config();
3535+
3536+
let chanmon_cfgs = create_chanmon_cfgs(2);
3537+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
3538+
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
3539+
100;
3540+
3541+
let channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
3542+
3543+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
3544+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
3545+
3546+
const FEERATE: u32 = 253;
3547+
const TOTAL_ANCHORS_MSAT: u64 = 2 * 330_000;
3548+
const NODE_0_DUST_LIMIT_MSAT: u64 = 10_000 * 1000;
3549+
const NODE_1_DUST_LIMIT_MSAT: u64 = 354 * 1000;
3550+
const CHANNEL_VALUE_MSAT: u64 = 50_000 * 1000;
3551+
const NODE_0_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000;
3552+
const NODE_1_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000;
3553+
const NODE_0_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 1_000 * 1_000;
3554+
const NODE_1_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 10_000 * 1_000;
3555+
3556+
let channel_id = create_announced_chan_between_nodes_with_value(
3557+
&nodes,
3558+
0,
3559+
1,
3560+
CHANNEL_VALUE_MSAT / 1000,
3561+
NODE_1_VALUE_TO_SELF_MSAT,
3562+
)
3563+
.2;
3564+
assert_eq!(nodes[0].node.list_channels()[0].channel_type.as_ref().unwrap(), &channel_type);
3565+
3566+
{
3567+
let per_peer_state_lock;
3568+
let mut peer_state_lock;
3569+
let chan =
3570+
get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id);
3571+
chan.context_mut().holder_dust_limit_satoshis = NODE_0_DUST_LIMIT_MSAT / 1000;
3572+
chan.funding_mut().counterparty_selected_channel_reserve_satoshis =
3573+
Some(NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000);
3574+
assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_1_DUST_LIMIT_MSAT / 1000);
3575+
assert_eq!(
3576+
chan.funding().holder_selected_channel_reserve_satoshis,
3577+
NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000
3578+
);
3579+
}
3580+
3581+
{
3582+
let per_peer_state_lock;
3583+
let mut peer_state_lock;
3584+
let chan =
3585+
get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id);
3586+
chan.context_mut().counterparty_dust_limit_satoshis = NODE_0_DUST_LIMIT_MSAT / 1000;
3587+
chan.funding_mut().holder_selected_channel_reserve_satoshis =
3588+
NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000;
3589+
assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_1_DUST_LIMIT_MSAT / 1000);
3590+
assert_eq!(
3591+
chan.funding().counterparty_selected_channel_reserve_satoshis,
3592+
Some(NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000)
3593+
);
3594+
}
3595+
3596+
// This HTLC is only present on node 1's commitment
3597+
const SNEAKY_HTLC_MSAT: u64 = 5_000_000;
3598+
3599+
route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT);
3600+
3601+
let node_1_details = &nodes[1].node.list_channels()[0];
3602+
let expected_outbound_capacity_msat =
3603+
NODE_1_VALUE_TO_SELF_MSAT - SNEAKY_HTLC_MSAT - NODE_0_SELECTED_CHANNEL_RESERVE_MSAT;
3604+
assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3605+
let expected_available_capacity_msat = expected_outbound_capacity_msat;
3606+
assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat);
3607+
let expected_splice_out_max =
3608+
NODE_1_VALUE_TO_SELF_MSAT / 1000 - SNEAKY_HTLC_MSAT / 1000 - NODE_1_DUST_LIMIT_MSAT / 1000;
3609+
assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max);
3610+
3611+
let node_0_details = &nodes[0].node.list_channels()[0];
3612+
let expected_outbound_capacity_msat =
3613+
NODE_0_VALUE_TO_SELF_MSAT - NODE_1_SELECTED_CHANNEL_RESERVE_MSAT - TOTAL_ANCHORS_MSAT;
3614+
assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3615+
let expected_available_capacity_msat =
3616+
expected_outbound_capacity_msat - commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000;
3617+
assert_eq!(node_0_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat);
3618+
let expected_splice_out_max = NODE_0_VALUE_TO_SELF_MSAT / 1000
3619+
- TOTAL_ANCHORS_MSAT / 1000
3620+
- commit_tx_fee_sat(FEERATE, 2, &channel_type)
3621+
- NODE_0_DUST_LIMIT_MSAT / 1000;
3622+
assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_splice_out_max);
3623+
3624+
let node_0_payment_msat = expected_available_capacity_msat;
3625+
send_payment(&nodes[0], &[&nodes[1]], node_0_payment_msat);
3626+
3627+
route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT);
3628+
route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT);
3629+
3630+
let node_0_details = &nodes[0].node.list_channels()[0];
3631+
let expected_outbound_capacity_msat = NODE_0_VALUE_TO_SELF_MSAT
3632+
- node_0_payment_msat
3633+
- NODE_1_SELECTED_CHANNEL_RESERVE_MSAT
3634+
- TOTAL_ANCHORS_MSAT;
3635+
assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3636+
assert_eq!(
3637+
node_0_details.outbound_capacity_msat,
3638+
commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000
3639+
);
3640+
assert_eq!(node_0_details.next_outbound_htlc_limit_msat, 0);
3641+
assert_eq!(node_0_details.next_splice_out_maximum_sat, 0);
3642+
3643+
let node_1_details = &nodes[1].node.list_channels()[0];
3644+
let expected_outbound_capacity_msat = NODE_1_VALUE_TO_SELF_MSAT + node_0_payment_msat
3645+
- 3 * SNEAKY_HTLC_MSAT
3646+
- NODE_0_SELECTED_CHANNEL_RESERVE_MSAT;
3647+
assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3648+
let (_htlc_success_tx_fee_sat, htlc_timeout_tx_fee_sat) =
3649+
second_stage_tx_fees_sat(&channel_type, FEERATE);
3650+
let expected_available_capacity_msat =
3651+
(NODE_1_DUST_LIMIT_MSAT / 1000 + htlc_timeout_tx_fee_sat) * 1000 - 1;
3652+
assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat);
3653+
let expected_splice_out_max = NODE_1_VALUE_TO_SELF_MSAT / 1000 + node_0_payment_msat / 1000
3654+
- 3 * SNEAKY_HTLC_MSAT / 1000
3655+
- NODE_1_DUST_LIMIT_MSAT / 1000;
3656+
assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max);
3657+
}
3658+
3659+
#[xtest(feature = "_externalize_tests")]
3660+
fn test_available_balances_both_commitments_dust_on_fundee_commitment() {
3661+
let mut config = test_default_channel_config();
3662+
3663+
let chanmon_cfgs = create_chanmon_cfgs(2);
3664+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
3665+
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
3666+
100;
3667+
3668+
let channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
3669+
3670+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
3671+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
3672+
3673+
const FEERATE: u32 = 253;
3674+
const TOTAL_ANCHORS_MSAT: u64 = 2 * 330_000;
3675+
const NODE_0_DUST_LIMIT_MSAT: u64 = 354 * 1000;
3676+
const NODE_1_DUST_LIMIT_MSAT: u64 = 10_000 * 1000;
3677+
const CHANNEL_VALUE_MSAT: u64 = 50_000 * 1000;
3678+
const NODE_0_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000;
3679+
const NODE_1_VALUE_TO_SELF_MSAT: u64 = 25_000 * 1000;
3680+
const NODE_0_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 10_000 * 1_000;
3681+
const NODE_1_SELECTED_CHANNEL_RESERVE_MSAT: u64 = 1_000 * 1_000;
3682+
3683+
let channel_id = create_announced_chan_between_nodes_with_value(
3684+
&nodes,
3685+
0,
3686+
1,
3687+
CHANNEL_VALUE_MSAT / 1000,
3688+
NODE_1_VALUE_TO_SELF_MSAT,
3689+
)
3690+
.2;
3691+
assert_eq!(nodes[0].node.list_channels()[0].channel_type.as_ref().unwrap(), &channel_type);
3692+
3693+
{
3694+
let per_peer_state_lock;
3695+
let mut peer_state_lock;
3696+
let chan =
3697+
get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, channel_id);
3698+
chan.context_mut().counterparty_dust_limit_satoshis = NODE_1_DUST_LIMIT_MSAT / 1000;
3699+
chan.funding_mut().holder_selected_channel_reserve_satoshis =
3700+
NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000;
3701+
assert_eq!(chan.context().holder_dust_limit_satoshis, NODE_0_DUST_LIMIT_MSAT / 1000);
3702+
assert_eq!(
3703+
chan.funding().counterparty_selected_channel_reserve_satoshis,
3704+
Some(NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000)
3705+
);
3706+
}
3707+
3708+
{
3709+
let per_peer_state_lock;
3710+
let mut peer_state_lock;
3711+
let chan =
3712+
get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, channel_id);
3713+
chan.context_mut().holder_dust_limit_satoshis = NODE_1_DUST_LIMIT_MSAT / 1000;
3714+
chan.funding_mut().counterparty_selected_channel_reserve_satoshis =
3715+
Some(NODE_0_SELECTED_CHANNEL_RESERVE_MSAT / 1000);
3716+
assert_eq!(chan.context().counterparty_dust_limit_satoshis, NODE_0_DUST_LIMIT_MSAT / 1000);
3717+
assert_eq!(
3718+
chan.funding().holder_selected_channel_reserve_satoshis,
3719+
NODE_1_SELECTED_CHANNEL_RESERVE_MSAT / 1000
3720+
);
3721+
}
3722+
3723+
// This HTLC is only present on node 0's commitment
3724+
const SNEAKY_HTLC_MSAT: u64 = 5_000_000;
3725+
3726+
route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT);
3727+
3728+
let node_1_details = &nodes[1].node.list_channels()[0];
3729+
let expected_outbound_capacity_msat =
3730+
NODE_1_VALUE_TO_SELF_MSAT - SNEAKY_HTLC_MSAT - NODE_0_SELECTED_CHANNEL_RESERVE_MSAT;
3731+
assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3732+
let expected_available_capacity_msat = expected_outbound_capacity_msat;
3733+
assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat);
3734+
let expected_splice_out_max =
3735+
NODE_1_VALUE_TO_SELF_MSAT / 1000 - SNEAKY_HTLC_MSAT / 1000 - NODE_1_DUST_LIMIT_MSAT / 1000;
3736+
assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max);
3737+
3738+
let node_0_details = &nodes[0].node.list_channels()[0];
3739+
3740+
let expected_outbound_capacity_msat =
3741+
NODE_0_VALUE_TO_SELF_MSAT - NODE_1_SELECTED_CHANNEL_RESERVE_MSAT - TOTAL_ANCHORS_MSAT;
3742+
assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3743+
3744+
let expected_splice_out_max = NODE_0_VALUE_TO_SELF_MSAT / 1000
3745+
- TOTAL_ANCHORS_MSAT / 1000
3746+
- commit_tx_fee_sat(FEERATE, 2, &channel_type)
3747+
- NODE_0_DUST_LIMIT_MSAT / 1000;
3748+
assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_splice_out_max);
3749+
3750+
let expected_available_capacity_msat =
3751+
expected_outbound_capacity_msat - commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000;
3752+
assert_eq!(node_0_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat);
3753+
3754+
let node_0_payment_msat = expected_available_capacity_msat;
3755+
send_payment(&nodes[0], &[&nodes[1]], node_0_payment_msat);
3756+
3757+
route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT);
3758+
route_payment(&nodes[1], &[&nodes[0]], SNEAKY_HTLC_MSAT);
3759+
3760+
let node_0_details = &nodes[0].node.list_channels()[0];
3761+
let expected_outbound_capacity_msat = NODE_0_VALUE_TO_SELF_MSAT
3762+
- node_0_payment_msat
3763+
- NODE_1_SELECTED_CHANNEL_RESERVE_MSAT
3764+
- TOTAL_ANCHORS_MSAT;
3765+
assert_eq!(node_0_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3766+
assert_eq!(
3767+
node_0_details.outbound_capacity_msat,
3768+
commit_tx_fee_sat(FEERATE, 3, &channel_type) * 1000
3769+
);
3770+
assert_eq!(node_0_details.next_outbound_htlc_limit_msat, 0);
3771+
3772+
let local_balance_before_fee_sat =
3773+
NODE_0_VALUE_TO_SELF_MSAT / 1000 - node_0_payment_msat / 1000 - TOTAL_ANCHORS_MSAT / 1000;
3774+
let post_splice_delta_above_reserve = commit_tx_fee_sat(FEERATE, 4, &channel_type);
3775+
let divident_sat = local_balance_before_fee_sat * 100 + 100
3776+
- (post_splice_delta_above_reserve * 100)
3777+
- CHANNEL_VALUE_MSAT / 1000;
3778+
let expected_splice_out_max = (divident_sat - 1) / 99;
3779+
assert_eq!(node_0_details.next_splice_out_maximum_sat, expected_splice_out_max);
3780+
3781+
let node_1_details = &nodes[1].node.list_channels()[0];
3782+
let expected_outbound_capacity_msat = NODE_1_VALUE_TO_SELF_MSAT + node_0_payment_msat
3783+
- 3 * SNEAKY_HTLC_MSAT
3784+
- NODE_0_SELECTED_CHANNEL_RESERVE_MSAT;
3785+
assert_eq!(node_1_details.outbound_capacity_msat, expected_outbound_capacity_msat);
3786+
let (htlc_success_tx_fee_sat, _htlc_timeout_tx_fee_sat) =
3787+
second_stage_tx_fees_sat(&channel_type, FEERATE);
3788+
let expected_available_capacity_msat =
3789+
(NODE_0_DUST_LIMIT_MSAT / 1000 + htlc_success_tx_fee_sat) * 1000 - 1;
3790+
assert_eq!(node_1_details.next_outbound_htlc_limit_msat, expected_available_capacity_msat);
3791+
let expected_splice_out_max = NODE_1_VALUE_TO_SELF_MSAT / 1000 + node_0_payment_msat / 1000
3792+
- 3 * SNEAKY_HTLC_MSAT / 1000
3793+
- NODE_1_DUST_LIMIT_MSAT / 1000;
3794+
assert_eq!(node_1_details.next_splice_out_maximum_sat, expected_splice_out_max);
3795+
}

0 commit comments

Comments
 (0)