Skip to content

Commit 034892b

Browse files
committed
Rework max commitment transaction balance debug assertions
These assertions made sure that our balance would never dip below the reserve, and if they ever were, that the balance must only move towards meeting the reserve. With splicing, this doesn't always work, as a node that is not interested in contributing could end up below the reserve of the post-splice channel. Therefore, we rework these assertions such that we only keep track of the previous commitment transaction balance, and compare against the current, ensuring that our balance only increases when below the reserve.
1 parent 52c76ab commit 034892b

2 files changed

Lines changed: 84 additions & 24 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 33 additions & 24 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,16 +2847,16 @@ impl FundingScope {
28472847
counterparty_selected_channel_reserve_satoshis,
28482848
holder_selected_channel_reserve_satoshis,
28492849
#[cfg(debug_assertions)]
2850-
holder_max_commitment_tx_output: {
2851-
let prev = *prev_funding.holder_max_commitment_tx_output.lock().unwrap();
2850+
holder_prev_commitment_tx_balance: {
2851+
let prev = *prev_funding.holder_prev_commitment_tx_balance.lock().unwrap();
28522852
Mutex::new((
28532853
prev.0.saturating_add_signed(our_funding_contribution.to_sat() * 1000),
28542854
prev.1.saturating_add_signed(their_funding_contribution.to_sat() * 1000),
28552855
))
28562856
},
28572857
#[cfg(debug_assertions)]
2858-
counterparty_max_commitment_tx_output: {
2859-
let prev = *prev_funding.counterparty_max_commitment_tx_output.lock().unwrap();
2858+
counterparty_prev_commitment_tx_balance: {
2859+
let prev = *prev_funding.counterparty_prev_commitment_tx_balance.lock().unwrap();
28602860
Mutex::new((
28612861
prev.0.saturating_add_signed(our_funding_contribution.to_sat() * 1000),
28622862
prev.1.saturating_add_signed(their_funding_contribution.to_sat() * 1000),
@@ -3805,9 +3805,9 @@ impl<SP: SignerProvider> ChannelContext<SP> {
38053805
holder_selected_channel_reserve_satoshis,
38063806

38073807
#[cfg(debug_assertions)]
3808-
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))),
38093809
#[cfg(debug_assertions)]
3810-
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))),
38113811

38123812
#[cfg(any(test, fuzzing))]
38133813
next_local_fee: Mutex::new(PredictedNextFee::default()),
@@ -4043,9 +4043,9 @@ impl<SP: SignerProvider> ChannelContext<SP> {
40434043
// We'll add our counterparty's `funding_satoshis` to these max commitment output assertions
40444044
// when we receive `accept_channel2`.
40454045
#[cfg(debug_assertions)]
4046-
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)),
40474047
#[cfg(debug_assertions)]
4048-
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)),
40494049

40504050
#[cfg(any(test, fuzzing))]
40514051
next_local_fee: Mutex::new(PredictedNextFee::default()),
@@ -5594,17 +5594,26 @@ impl<SP: SignerProvider> ChannelContext<SP> {
55945594
{
55955595
// Make sure that the to_self/to_remote is always either past the appropriate
55965596
// channel_reserve *or* it is making progress towards it.
5597-
let mut broadcaster_max_commitment_tx_output = if generated_by_local {
5598-
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()
55995599
} else {
5600-
funding.counterparty_max_commitment_tx_output.lock().unwrap()
5600+
funding.counterparty_prev_commitment_tx_balance.lock().unwrap()
56015601
};
5602-
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());
5603-
broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, stats.local_balance_before_fee_msat);
5604-
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);
5605-
broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, stats.remote_balance_before_fee_msat);
5606-
}
56075602

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+
}
56085617

56095618
// This populates the HTLC-source table with the indices from the HTLCs in the commitment
56105619
// transaction.
@@ -15983,9 +15992,9 @@ impl<'a, 'b, 'c, ES: EntropySource, SP: SignerProvider>
1598315992
.unwrap(),
1598415993

1598515994
#[cfg(debug_assertions)]
15986-
holder_max_commitment_tx_output: Mutex::new((0, 0)),
15995+
holder_prev_commitment_tx_balance: Mutex::new((0, 0)),
1598715996
#[cfg(debug_assertions)]
15988-
counterparty_max_commitment_tx_output: Mutex::new((0, 0)),
15997+
counterparty_prev_commitment_tx_balance: Mutex::new((0, 0)),
1598915998

1599015999
#[cfg(any(test, fuzzing))]
1599116000
next_local_fee: Mutex::new(PredictedNextFee::default()),
@@ -18548,9 +18557,9 @@ mod tests {
1854818557
holder_selected_channel_reserve_satoshis: 0,
1854918558

1855018559
#[cfg(debug_assertions)]
18551-
holder_max_commitment_tx_output: Mutex::new((0, 0)),
18560+
holder_prev_commitment_tx_balance: Mutex::new((0, 0)),
1855218561
#[cfg(debug_assertions)]
18553-
counterparty_max_commitment_tx_output: Mutex::new((0, 0)),
18562+
counterparty_prev_commitment_tx_balance: Mutex::new((0, 0)),
1855418563

1855518564
#[cfg(any(test, fuzzing))]
1855618565
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
@@ -2730,3 +2730,54 @@ fn test_splice_buffer_invalid_commitment_signed_closes_channel() {
27302730
);
27312731
check_added_monitors(&nodes[0], 1);
27322732
}
2733+
2734+
#[test]
2735+
fn test_splice_balance_falls_below_reserve() {
2736+
// Test that we're able to proceed with a splice where the acceptor does not contribute
2737+
// anything, but the initiator does, resulting in an increased channel reserve that the
2738+
// counterparty does not meet but is still valid.
2739+
let chanmon_cfgs = create_chanmon_cfgs(2);
2740+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
2741+
let mut config = test_default_channel_config();
2742+
config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
2743+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
2744+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
2745+
2746+
let initial_channel_value_sat = 100_000;
2747+
// Push 10k sat to node 1 so it has balance to send HTLCs back.
2748+
let push_msat = 10_000_000;
2749+
let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
2750+
&nodes,
2751+
0,
2752+
1,
2753+
initial_channel_value_sat,
2754+
push_msat,
2755+
);
2756+
2757+
let _ = provide_anchor_reserves(&nodes);
2758+
2759+
// Create bidirectional pending HTLCs (routed but not claimed).
2760+
// Outbound HTLC from node 0 to node 1.
2761+
let (preimage_0_to_1, _hash_0_to_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
2762+
// Large inbound HTLC from node 1 to node 0, bringing node 1's remaining balance down to
2763+
// 2000 sat. The old reserve (1% of 100k) is 1000 sat so this is still above reserve.
2764+
let (preimage_1_to_0, _hash_1_to_0, ..) = route_payment(&nodes[1], &[&nodes[0]], 8_000_000);
2765+
2766+
// Splice-in 200k sat. The new channel value becomes 300k sat, raising the reserve to 3000
2767+
// sat. Node 1's remaining 2000 sat is now below the new reserve.
2768+
let initiator_contribution =
2769+
initiate_splice_in(&nodes[0], &nodes[1], channel_id, Amount::from_sat(200_000));
2770+
let (splice_tx, _) = splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution);
2771+
2772+
// Confirm and lock the splice.
2773+
mine_transaction(&nodes[0], &splice_tx);
2774+
mine_transaction(&nodes[1], &splice_tx);
2775+
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
2776+
2777+
// Claim both pending HTLCs to verify the channel is fully functional after the splice.
2778+
claim_payment(&nodes[0], &[&nodes[1]], preimage_0_to_1);
2779+
claim_payment(&nodes[1], &[&nodes[0]], preimage_1_to_0);
2780+
2781+
// Final sanity check: send a payment using the new spliced capacity.
2782+
let _ = send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
2783+
}

0 commit comments

Comments
 (0)