Skip to content

Commit 4bfdb35

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 b5923c7 commit 4bfdb35

File tree

1 file changed

+20
-43
lines changed

1 file changed

+20
-43
lines changed

lightning/src/ln/channel.rs

Lines changed: 20 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,12 +1097,12 @@ pub enum AnnouncementSigsState {
10971097
/// An enum indicating whether the local or remote side offered a given HTLC.
10981098
enum HTLCInitiator {
10991099
LocalOffered,
1100+
#[allow(dead_code)]
11001101
RemoteOffered,
11011102
}
11021103

11031104
/// Current counts of various HTLCs, useful for calculating current balances available exactly.
11041105
struct HTLCStats {
1105-
pending_inbound_htlcs: usize,
11061106
pending_outbound_htlcs: usize,
11071107
pending_inbound_htlcs_value_msat: u64,
11081108
pending_outbound_htlcs_value_msat: u64,
@@ -4111,7 +4111,6 @@ where
41114111
///
41124112
/// We take the conservative approach and only assume that a HTLC will
41134113
/// not be in the next commitment when it is guaranteed that it won't be.
4114-
#[allow(dead_code)]
41154114
#[rustfmt::skip]
41164115
fn get_next_commitment_htlcs(
41174116
&self, local: bool, htlc_candidate: Option<HTLCAmountDirection>, include_counterparty_unknown_htlcs: bool,
@@ -4181,7 +4180,6 @@ where
41814180
/// will *not* be present on the next commitment from `next_commitment_htlcs`, and
41824181
/// check if their outcome is successful. If it is, we add the value of this claimed
41834182
/// HTLC to the balance of the claimer.
4184-
#[allow(dead_code)]
41854183
#[rustfmt::skip]
41864184
fn get_next_commitment_value_to_self_msat(&self, local: bool, funding: &FundingScope) -> u64 {
41874185
let inbound_claimed_htlc_msat: u64 =
@@ -4213,7 +4211,6 @@ where
42134211
.saturating_add(inbound_claimed_htlc_msat)
42144212
}
42154213

4216-
#[allow(dead_code)]
42174214
fn get_next_local_commitment_stats(
42184215
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
42194216
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
@@ -4239,7 +4236,6 @@ where
42394236
)
42404237
}
42414238

4242-
#[allow(dead_code)]
42434239
fn get_next_remote_commitment_stats(
42444240
&self, funding: &FundingScope, htlc_candidate: Option<HTLCAmountDirection>,
42454241
include_counterparty_unknown_htlcs: bool, addl_nondust_htlc_count: usize,
@@ -4280,15 +4276,25 @@ where
42804276
let dust_exposure_limiting_feerate = self.get_dust_exposure_limiting_feerate(
42814277
&fee_estimator, funding.get_channel_type(),
42824278
);
4283-
let htlc_stats = self.get_pending_htlc_stats(funding, None, dust_exposure_limiting_feerate);
4284-
if htlc_stats.pending_inbound_htlcs + 1 > self.holder_max_accepted_htlcs as usize {
4279+
// 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)
4280+
let include_counterparty_unknown_htlcs = false;
4281+
// Don't include the extra fee spike buffer HTLC in calculations
4282+
let fee_spike_buffer_htlc = 0;
4283+
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);
4284+
4285+
if next_remote_commitment_stats.inbound_htlcs_count > self.holder_max_accepted_htlcs as usize {
42854286
return Err(ChannelError::close(format!("Remote tried to push more than our max accepted HTLCs ({})", self.holder_max_accepted_htlcs)));
42864287
}
4287-
if htlc_stats.pending_inbound_htlcs_value_msat + msg.amount_msat > self.holder_max_htlc_value_in_flight_msat {
4288+
if next_remote_commitment_stats.inbound_htlcs_value_msat > self.holder_max_htlc_value_in_flight_msat {
42884289
return Err(ChannelError::close(format!("Remote HTLC add would put them over our max HTLC value ({})", self.holder_max_htlc_value_in_flight_msat)));
42894290
}
42904291

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

4321+
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, 0, self.feerate_per_kw, dust_exposure_limiting_feerate);
43424322
if funding.is_outbound() {
43434323
// Check that they won't violate our local required channel reserve by adding this HTLC.
4344-
let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
4345-
let local_commit_tx_fee_msat = self.next_local_commit_tx_fee_msat(funding, htlc_candidate, None);
4346-
if local_balance_before_fee_msat < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat {
4324+
if next_local_commitment_stats.holder_balance_msat.unwrap_or(0) < funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + next_local_commitment_stats.commit_tx_fee_sat * 1000 {
43474325
return Err(ChannelError::close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned()));
43484326
}
43494327
}
@@ -4956,7 +4934,6 @@ where
49564934
});
49574935

49584936
HTLCStats {
4959-
pending_inbound_htlcs: self.pending_inbound_htlcs.len(),
49604937
pending_outbound_htlcs,
49614938
pending_inbound_htlcs_value_msat,
49624939
pending_outbound_htlcs_value_msat,

0 commit comments

Comments
 (0)