Skip to content

Commit 48b412a

Browse files
committed
Improve prediction of commitment stats in validate_update_add_htlc
`ChannelContext::get_pending_htlc_stats` predicts that the set of HTLCs on the next commitment will be all the HTLCs in `ChannelContext.pending_inbound_htlcs`, and `ChannelContext.pending_outbound_htlcs`, as well as all the outbound HTLC adds in the holding cell. This is an overestimate: * Outbound HTLC removals which have been ACK'ed by the counterparty will certainly not be present in any *next* commitment, even though they remain in `pending_outbound_htlcs`. * Outbound HTLCs in the `RemoteRemoved` state, will not be present in the next *local* commitment. * Outbound HTLCs in the `LocalAnnounced` state have no guarantee that they were received by the counterparty before she sent the `update_fee`. * Outbound `update_add_htlc`'s in the holding cell are certainly not known by the counterparty, and we will reevaluate their addition to the channel when freeing the holding cell. * Inbound HTLCs in the `LocalRemoved` state will not be present in the next *remote* commitment. `ChannelContext::next_local_commit_tx_fee_msat` over-counts outbound HTLCs in the `LocalAnnounced` and `RemoteRemoved` states, as well as outbound `update_add_htlc`'s in the holding cell. `ChannelContext::next_remote_commit_tx_fee_msat` over-counts inbound HTLCs in the `LocalRemoved` state, as well as outbound HTLCs in the `LocalAnnounced` state. This commit stops using these functions in favor of the newly added `ChannelContext::get_next_{local, remote}_commitment_stats` methods, and fixes the issues described above. If we are the funder, we also check that adding this inbound HTLC doesn't increase the commitment transaction fee to the point of exhausting our balance on the local commitment. Previously, we would only subtract the anchors from `funding.value_to_self_msat`; we now also subtract the outbound HTLCs on the next local commitment from `funding.value_to_self_msat` before checking if we can afford the additional transaction fees. Inbound `LocalRemoved` HTLCs that were **not** successful are now credited to `remote_balance_before_fee_msat` as they will certainly not be on the next remote commitment. We previously debited these from the remote balance to arrive at `remote_balance_before_fee_msat`. When calculating dust exposure, we now take a buffer from the currently committed feerate, and ignore any fee updates in `ChannelContext.pending_update_fee`.
1 parent 06ed9cb commit 48b412a

File tree

1 file changed

+21
-43
lines changed

1 file changed

+21
-43
lines changed

lightning/src/ln/channel.rs

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,12 +1101,12 @@ pub enum AnnouncementSigsState {
11011101
/// An enum indicating whether the local or remote side offered a given HTLC.
11021102
enum HTLCInitiator {
11031103
LocalOffered,
1104+
#[allow(dead_code)]
11041105
RemoteOffered,
11051106
}
11061107

11071108
/// Current counts of various HTLCs, useful for calculating current balances available exactly.
11081109
struct HTLCStats {
1109-
pending_inbound_htlcs: usize,
11101110
pending_outbound_htlcs: usize,
11111111
pending_inbound_htlcs_value_msat: u64,
11121112
pending_outbound_htlcs_value_msat: u64,
@@ -4107,7 +4107,6 @@ where
41074107
///
41084108
/// We take the conservative approach and only assume that a HTLC will
41094109
/// not be in the next commitment when it is guaranteed that it won't be.
4110-
#[allow(dead_code)]
41114110
#[rustfmt::skip]
41124111
fn get_next_commitment_htlcs(
41134112
&self, local: bool, htlc_candidate: Option<HTLCAmountDirection>, include_counterparty_unknown_htlcs: bool,
@@ -4177,7 +4176,6 @@ where
41774176
/// will *not* be present on the next commitment from `next_commitment_htlcs`, and
41784177
/// check if their outcome is successful. If it is, we add the value of this claimed
41794178
/// HTLC to the balance of the claimer.
4180-
#[allow(dead_code)]
41814179
#[rustfmt::skip]
41824180
fn get_next_commitment_value_to_self_msat(&self, local: bool, funding: &FundingScope) -> u64 {
41834181
let inbound_claimed_htlc_msat: u64 =
@@ -4209,7 +4207,6 @@ where
42094207
.saturating_add(inbound_claimed_htlc_msat)
42104208
}
42114209

4212-
#[allow(dead_code)]
42134210
fn get_next_local_commitment_stats(
42144211
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
42154212
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
@@ -4235,7 +4232,6 @@ where
42354232
)
42364233
}
42374234

4238-
#[allow(dead_code)]
42394235
fn get_next_remote_commitment_stats(
42404236
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
42414237
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
@@ -4276,15 +4272,25 @@ where
42764272
let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(
42774273
&fee_estimator, funding.get_channel_type(),
42784274
);
4279-
let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate);
4280-
if htlc_stats.pending_inbound_htlcs + 1 > self.holder_max_accepted_htlcs as usize {
4275+
// Don't include outbound update_add_htlc's in the holding cell, or those which haven't yet been ACK'ed by the counterparty (ie. LocalAnnounced HTLCs)
4276+
let include_counterparty_unknown_htlcs = false;
4277+
// Don't include the extra fee spike buffer HTLC in calculations
4278+
let fee_spike_buffer_htlc = 0;
4279+
let next_remote_commitment_stats = self.get_next_remote_commitment_stats(funding, Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }), include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate);
4280+
4281+
if next_remote_commitment_stats.inbound_htlcs_count > self.holder_max_accepted_htlcs as usize {
42814282
return Err(ChannelError::close(format!("Remote tried to push more than our max accepted HTLCs ({})", self.holder_max_accepted_htlcs)));
42824283
}
4283-
if htlc_stats.pending_inbound_htlcs_value_msat + msg.amount_msat > self.holder_max_htlc_value_in_flight_msat {
4284+
if next_remote_commitment_stats.inbound_htlcs_value_msat > self.holder_max_htlc_value_in_flight_msat {
42844285
return Err(ChannelError::close(format!("Remote HTLC add would put them over our max HTLC value ({})", self.holder_max_htlc_value_in_flight_msat)));
42854286
}
42864287

4287-
// Check holder_selected_channel_reserve_satoshis (we're getting paid, so they have to at least meet
4288+
let remote_balance_before_fee_msat = next_remote_commitment_stats.counterparty_balance_before_fee_msat.ok_or(ChannelError::close("Remote HTLC add would overdraw remaining funds".to_owned()))?;
4289+
4290+
// Check that the remote can afford to pay for this HTLC on-chain at the current
4291+
// feerate_per_kw, while maintaining their channel reserve (as required by the spec).
4292+
//
4293+
// We check holder_selected_channel_reserve_satoshis (we're getting paid, so they have to at least meet
42884294
// the reserve_satoshis we told them to always have as direct payment so that they lose
42894295
// something if we punish them for broadcasting an old state).
42904296
// Note that we don't really care about having a small/no to_remote output in our local
@@ -4296,50 +4302,23 @@ where
42964302
// violate the reserve value if we do not do this (as we forget inbound HTLCs from the
42974303
// Channel state once they will not be present in the next received commitment
42984304
// transaction).
4299-
let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = {
4300-
let removed_outbound_total_msat: u64 = self.pending_outbound_htlcs
4301-
.iter()
4302-
.filter_map(|htlc| {
4303-
matches!(
4304-
htlc.state,
4305-
OutboundHTLCState::AwaitingRemoteRevokeToRemove(OutboundHTLCOutcome::Success(_, _))
4306-
| OutboundHTLCState::AwaitingRemovedRemoteRevoke(OutboundHTLCOutcome::Success(_, _))
4307-
)
4308-
.then_some(htlc.amount_msat)
4309-
})
4310-
.sum();
4311-
let pending_value_to_self_msat =
4312-
funding.value_to_self_msat + htlc_stats.pending_inbound_htlcs_value_msat - removed_outbound_total_msat;
4313-
let pending_remote_value_msat =
4314-
funding.get_value_satoshis() * 1000 - pending_value_to_self_msat;
4315-
4316-
// Subtract any non-HTLC outputs from the local and remote balances
4317-
SpecTxBuilder {}.subtract_non_htlc_outputs(funding.is_outbound(), funding.value_to_self_msat, pending_remote_value_msat, funding.get_channel_type())
4318-
};
4319-
if remote_balance_before_fee_msat < msg.amount_msat {
4320-
return Err(ChannelError::close("Remote HTLC add would overdraw remaining funds".to_owned()));
4321-
}
4322-
4323-
// Check that the remote can afford to pay for this HTLC on-chain at the current
4324-
// feerate_per_kw, while maintaining their channel reserve (as required by the spec).
43254305
{
43264306
let remote_commit_tx_fee_msat = if funding.is_outbound() { 0 } else {
4327-
let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
4328-
self.next_remote_commit_tx_fee_msat(funding, Some(htlc_candidate), None) // Don't include the extra fee spike buffer HTLC in calculations
4307+
next_remote_commitment_stats.commit_tx_fee_sat * 1000
43294308
};
4330-
if remote_balance_before_fee_msat.saturating_sub(msg.amount_msat) < remote_commit_tx_fee_msat {
4309+
if remote_balance_before_fee_msat < remote_commit_tx_fee_msat {
43314310
return Err(ChannelError::close("Remote HTLC add would not leave enough to pay for fees".to_owned()));
43324311
};
4333-
if remote_balance_before_fee_msat.saturating_sub(msg.amount_msat).saturating_sub(remote_commit_tx_fee_msat) < funding.holder_selected_channel_reserve_satoshis * 1000 {
4312+
if remote_balance_before_fee_msat.saturating_sub(remote_commit_tx_fee_msat) < funding.holder_selected_channel_reserve_satoshis * 1000 {
43344313
return Err(ChannelError::close("Remote HTLC add would put them under remote reserve value".to_owned()));
43354314
}
43364315
}
43374316

43384317
if funding.is_outbound() {
4318+
let next_local_commitment_stats = self.get_next_local_commitment_stats(funding, Some(HTLCAmountDirection { outbound: false, amount_msat: msg.amount_msat }), include_counterparty_unknown_htlcs, fee_spike_buffer_htlc, self.feerate_per_kw, dust_exposure_limiting_feerate);
4319+
let holder_balance_msat = next_local_commitment_stats.holder_balance_before_fee_msat.expect("Adding an inbound HTLC should never exhaust the holder's balance before fees");
43394320
// Check that they won't violate our local required channel reserve by adding this HTLC.
4340-
let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
4341-
let local_commit_tx_fee_msat = self.next_local_commit_tx_fee_msat(funding, htlc_candidate, None);
4342-
if local_balance_before_fee_msat < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat {
4321+
if holder_balance_msat < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + next_local_commitment_stats.commit_tx_fee_sat * 1000 {
43434322
return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned()));
43444323
}
43454324
}
@@ -4952,7 +4931,6 @@ where
49524931
});
49534932

49544933
HTLCStats {
4955-
pending_inbound_htlcs: self.pending_inbound_htlcs.len(),
49564934
pending_outbound_htlcs,
49574935
pending_inbound_htlcs_value_msat,
49584936
pending_outbound_htlcs_value_msat,

0 commit comments

Comments
 (0)