Skip to content

Commit 62c7575

Browse files
authored
Merge pull request #4417 from wpaulino/fix-splice-funding-scope-max-commitment-output
Account for missing balance in splice max commitment output tracking
2 parents 8bbc5ec + 034892b commit 62c7575

File tree

2 files changed

+94
-28
lines changed

2 files changed

+94
-28
lines changed

lightning/src/ln/channel.rs

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2583,10 +2583,10 @@ pub(super) struct FundingScope {
25832583

25842584
#[cfg(debug_assertions)]
25852585
/// Max to_local and to_remote outputs in a locally-generated commitment transaction
2586-
holder_max_commitment_tx_output: Mutex<(u64, u64)>,
2586+
holder_prev_commitment_tx_balance: Mutex<(u64, u64)>,
25872587
#[cfg(debug_assertions)]
25882588
/// Max to_local and to_remote outputs in a remote-generated commitment transaction
2589-
counterparty_max_commitment_tx_output: Mutex<(u64, u64)>,
2589+
counterparty_prev_commitment_tx_balance: Mutex<(u64, u64)>,
25902590

25912591
// We save these values so we can make sure validation of channel updates properly predicts
25922592
// what the next commitment transaction fee will be, by comparing the cached values to the
@@ -2658,9 +2658,9 @@ impl Readable for FundingScope {
26582658
counterparty_selected_channel_reserve_satoshis,
26592659
holder_selected_channel_reserve_satoshis: holder_selected_channel_reserve_satoshis.0.unwrap(),
26602660
#[cfg(debug_assertions)]
2661-
holder_max_commitment_tx_output: Mutex::new((0, 0)),
2661+
holder_prev_commitment_tx_balance: Mutex::new((0, 0)),
26622662
#[cfg(debug_assertions)]
2663-
counterparty_max_commitment_tx_output: Mutex::new((0, 0)),
2663+
counterparty_prev_commitment_tx_balance: Mutex::new((0, 0)),
26642664
channel_transaction_parameters: channel_transaction_parameters.0.unwrap(),
26652665
funding_transaction,
26662666
funding_tx_confirmed_in,
@@ -2847,15 +2847,21 @@ impl FundingScope {
28472847
counterparty_selected_channel_reserve_satoshis,
28482848
holder_selected_channel_reserve_satoshis,
28492849
#[cfg(debug_assertions)]
2850-
holder_max_commitment_tx_output: Mutex::new((
2851-
post_value_to_self_msat,
2852-
(post_channel_value * 1000).saturating_sub(post_value_to_self_msat),
2853-
)),
2850+
holder_prev_commitment_tx_balance: {
2851+
let prev = *prev_funding.holder_prev_commitment_tx_balance.lock().unwrap();
2852+
Mutex::new((
2853+
prev.0.saturating_add_signed(our_funding_contribution.to_sat() * 1000),
2854+
prev.1.saturating_add_signed(their_funding_contribution.to_sat() * 1000),
2855+
))
2856+
},
28542857
#[cfg(debug_assertions)]
2855-
counterparty_max_commitment_tx_output: Mutex::new((
2856-
post_value_to_self_msat,
2857-
(post_channel_value * 1000).saturating_sub(post_value_to_self_msat),
2858-
)),
2858+
counterparty_prev_commitment_tx_balance: {
2859+
let prev = *prev_funding.counterparty_prev_commitment_tx_balance.lock().unwrap();
2860+
Mutex::new((
2861+
prev.0.saturating_add_signed(our_funding_contribution.to_sat() * 1000),
2862+
prev.1.saturating_add_signed(their_funding_contribution.to_sat() * 1000),
2863+
))
2864+
},
28592865
#[cfg(any(test, fuzzing))]
28602866
next_local_fee: Mutex::new(PredictedNextFee::default()),
28612867
#[cfg(any(test, fuzzing))]
@@ -3799,9 +3805,9 @@ impl<SP: SignerProvider> ChannelContext<SP> {
37993805
holder_selected_channel_reserve_satoshis,
38003806

38013807
#[cfg(debug_assertions)]
3802-
holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))),
3808+
holder_prev_commitment_tx_balance: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))),
38033809
#[cfg(debug_assertions)]
3804-
counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))),
3810+
counterparty_prev_commitment_tx_balance: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))),
38053811

38063812
#[cfg(any(test, fuzzing))]
38073813
next_local_fee: Mutex::new(PredictedNextFee::default()),
@@ -4037,9 +4043,9 @@ impl<SP: SignerProvider> ChannelContext<SP> {
40374043
// We'll add our counterparty's `funding_satoshis` to these max commitment output assertions
40384044
// when we receive `accept_channel2`.
40394045
#[cfg(debug_assertions)]
4040-
holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)),
4046+
holder_prev_commitment_tx_balance: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)),
40414047
#[cfg(debug_assertions)]
4042-
counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)),
4048+
counterparty_prev_commitment_tx_balance: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)),
40434049

40444050
#[cfg(any(test, fuzzing))]
40454051
next_local_fee: Mutex::new(PredictedNextFee::default()),
@@ -5588,17 +5594,26 @@ impl<SP: SignerProvider> ChannelContext<SP> {
55885594
{
55895595
// Make sure that the to_self/to_remote is always either past the appropriate
55905596
// channel_reserve *or* it is making progress towards it.
5591-
let mut broadcaster_max_commitment_tx_output = if generated_by_local {
5592-
funding.holder_max_commitment_tx_output.lock().unwrap()
5597+
let mut broadcaster_prev_commitment_balance = if generated_by_local {
5598+
funding.holder_prev_commitment_tx_balance.lock().unwrap()
55935599
} else {
5594-
funding.counterparty_max_commitment_tx_output.lock().unwrap()
5600+
funding.counterparty_prev_commitment_tx_balance.lock().unwrap()
55955601
};
5596-
debug_assert!(broadcaster_max_commitment_tx_output.0 <= stats.local_balance_before_fee_msat || stats.local_balance_before_fee_msat / 1000 >= funding.counterparty_selected_channel_reserve_satoshis.unwrap());
5597-
broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, stats.local_balance_before_fee_msat);
5598-
debug_assert!(broadcaster_max_commitment_tx_output.1 <= stats.remote_balance_before_fee_msat || stats.remote_balance_before_fee_msat / 1000 >= funding.holder_selected_channel_reserve_satoshis);
5599-
broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, stats.remote_balance_before_fee_msat);
5600-
}
56015602

5603+
if stats.local_balance_before_fee_msat / 1000 < funding.counterparty_selected_channel_reserve_satoshis.unwrap() {
5604+
// If the local balance is below the reserve on this new commitment, it MUST be
5605+
// greater than or equal to the one on the previous commitment.
5606+
debug_assert!(broadcaster_prev_commitment_balance.0 <= stats.local_balance_before_fee_msat);
5607+
}
5608+
broadcaster_prev_commitment_balance.0 = stats.local_balance_before_fee_msat;
5609+
5610+
if stats.remote_balance_before_fee_msat / 1000 < funding.holder_selected_channel_reserve_satoshis {
5611+
// If the remote balance is below the reserve on this new commitment, it MUST be
5612+
// greater than or equal to the one on the previous commitment.
5613+
debug_assert!(broadcaster_prev_commitment_balance.1 <= stats.remote_balance_before_fee_msat);
5614+
}
5615+
broadcaster_prev_commitment_balance.1 = stats.remote_balance_before_fee_msat;
5616+
}
56025617

56035618
// This populates the HTLC-source table with the indices from the HTLCs in the commitment
56045619
// transaction.
@@ -15977,9 +15992,9 @@ impl<'a, 'b, 'c, ES: EntropySource, SP: SignerProvider>
1597715992
.unwrap(),
1597815993

1597915994
#[cfg(debug_assertions)]
15980-
holder_max_commitment_tx_output: Mutex::new((0, 0)),
15995+
holder_prev_commitment_tx_balance: Mutex::new((0, 0)),
1598115996
#[cfg(debug_assertions)]
15982-
counterparty_max_commitment_tx_output: Mutex::new((0, 0)),
15997+
counterparty_prev_commitment_tx_balance: Mutex::new((0, 0)),
1598315998

1598415999
#[cfg(any(test, fuzzing))]
1598516000
next_local_fee: Mutex::new(PredictedNextFee::default()),
@@ -18542,9 +18557,9 @@ mod tests {
1854218557
holder_selected_channel_reserve_satoshis: 0,
1854318558

1854418559
#[cfg(debug_assertions)]
18545-
holder_max_commitment_tx_output: Mutex::new((0, 0)),
18560+
holder_prev_commitment_tx_balance: Mutex::new((0, 0)),
1854618561
#[cfg(debug_assertions)]
18547-
counterparty_max_commitment_tx_output: Mutex::new((0, 0)),
18562+
counterparty_prev_commitment_tx_balance: Mutex::new((0, 0)),
1854818563

1854918564
#[cfg(any(test, fuzzing))]
1855018565
next_local_fee: Mutex::new(PredictedNextFee::default()),

lightning/src/ln/splicing_tests.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2745,3 +2745,54 @@ fn test_splice_buffer_invalid_commitment_signed_closes_channel() {
27452745
);
27462746
check_added_monitors(&nodes[0], 1);
27472747
}
2748+
2749+
#[test]
2750+
fn test_splice_balance_falls_below_reserve() {
2751+
// Test that we're able to proceed with a splice where the acceptor does not contribute
2752+
// anything, but the initiator does, resulting in an increased channel reserve that the
2753+
// counterparty does not meet but is still valid.
2754+
let chanmon_cfgs = create_chanmon_cfgs(2);
2755+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
2756+
let mut config = test_default_channel_config();
2757+
config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
2758+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
2759+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
2760+
2761+
let initial_channel_value_sat = 100_000;
2762+
// Push 10k sat to node 1 so it has balance to send HTLCs back.
2763+
let push_msat = 10_000_000;
2764+
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
2765+
&nodes,
2766+
0,
2767+
1,
2768+
initial_channel_value_sat,
2769+
push_msat,
2770+
);
2771+
2772+
let _ = provide_anchor_reserves(&nodes);
2773+
2774+
// Create bidirectional pending HTLCs (routed but not claimed).
2775+
// Outbound HTLC from node 0 to node 1.
2776+
let (preimage_0_to_1, _hash_0_to_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
2777+
// Large inbound HTLC from node 1 to node 0, bringing node 1's remaining balance down to
2778+
// 2000 sat. The old reserve (1% of 100k) is 1000 sat so this is still above reserve.
2779+
let (preimage_1_to_0, _hash_1_to_0, ..) = route_payment(&nodes[1], &[&nodes[0]], 8_000_000);
2780+
2781+
// Splice-in 200k sat. The new channel value becomes 300k sat, raising the reserve to 3000
2782+
// sat. Node 1's remaining 2000 sat is now below the new reserve.
2783+
let initiator_contribution =
2784+
initiate_splice_in(&nodes[0], &nodes[1], channel_id, Amount::from_sat(200_000));
2785+
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution);
2786+
2787+
// Confirm and lock the splice.
2788+
mine_transaction(&nodes[0], &splice_tx);
2789+
mine_transaction(&nodes[1], &splice_tx);
2790+
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
2791+
2792+
// Claim both pending HTLCs to verify the channel is fully functional after the splice.
2793+
claim_payment(&nodes[0], &[&nodes[1]], preimage_0_to_1);
2794+
claim_payment(&nodes[1], &[&nodes[0]], preimage_1_to_0);
2795+
2796+
// Final sanity check: send a payment using the new spliced capacity.
2797+
let _ = send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
2798+
}

0 commit comments

Comments
 (0)