Skip to content

Commit 7b63215

Browse files
Don't include below-dust inbound HTLCs in commit tx fee calculation
Also remove part of the holding cell channel reserve test that's newly failing but a bit of a redundant test anyway.
1 parent 6dcb7c4 commit 7b63215

File tree

2 files changed

+255
-70
lines changed

2 files changed

+255
-70
lines changed

lightning/src/ln/channel.rs

Lines changed: 200 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,63 +1684,108 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
16841684
(COMMITMENT_TX_BASE_WEIGHT + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * self.feerate_per_kw as u64 / 1000 * 1000
16851685
}
16861686

1687-
// Get the commitment tx fee for the local (i.e our) next commitment transaction
1688-
// based on the number of pending HTLCs that are on track to be in our next
1689-
// commitment tx. `addl_htcs` is an optional parameter allowing the caller
1690-
// to add a number of additional HTLCs to the calculation. Note that dust
1691-
// HTLCs are excluded.
1692-
fn next_local_commit_tx_fee_msat(&self, addl_htlcs: usize) -> u64 {
1687+
// Get the commitment tx fee for the local's (i.e. our) next commitment transaction based on the
1688+
// number of pending HTLCs that are on track to be in our next commitment tx, plus an additional
1689+
// HTLC if `fee_spike_buffer_htlc` is Some, plus a new HTLC given by `new_htlc_amount`. Dust HTLCs
1690+
// are excluded.
1691+
fn next_local_commit_tx_fee_msat(&self, new_htlc_amount: u64, locally_offered: bool, fee_spike_buffer_htlc: Option<()>) -> u64 {
16931692
assert!(self.is_outbound());
16941693

1695-
let mut their_acked_htlcs = self.pending_inbound_htlcs.len();
1694+
let real_dust_limit_success_sat = (self.feerate_per_kw as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + self.holder_dust_limit_satoshis;
1695+
let real_dust_limit_timeout_sat = (self.feerate_per_kw as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + self.holder_dust_limit_satoshis;
1696+
1697+
let mut addl_htlcs = 0;
1698+
if fee_spike_buffer_htlc.is_some() { addl_htlcs += 1; }
1699+
if locally_offered && new_htlc_amount / 1000 >= real_dust_limit_timeout_sat {
1700+
addl_htlcs += 1;
1701+
} else if !locally_offered && new_htlc_amount / 1000 >= real_dust_limit_success_sat {
1702+
addl_htlcs += 1;
1703+
}
1704+
1705+
let mut included_htlcs = 0;
1706+
for ref htlc in self.pending_inbound_htlcs.iter() {
1707+
if htlc.amount_msat / 1000 < real_dust_limit_success_sat {
1708+
continue
1709+
}
1710+
// We include LocalRemoved HTLCs here because we may still need to broadcast a commitment
1711+
// transaction including this HTLC if it times out before they RAA.
1712+
included_htlcs += 1;
1713+
}
1714+
16961715
for ref htlc in self.pending_outbound_htlcs.iter() {
1697-
if htlc.amount_msat / 1000 <= self.holder_dust_limit_satoshis {
1716+
if htlc.amount_msat / 1000 < real_dust_limit_timeout_sat {
16981717
continue
16991718
}
17001719
match htlc.state {
1701-
OutboundHTLCState::Committed => their_acked_htlcs += 1,
1702-
OutboundHTLCState::RemoteRemoved {..} => their_acked_htlcs += 1,
1703-
OutboundHTLCState::LocalAnnounced {..} => their_acked_htlcs += 1,
1720+
OutboundHTLCState::LocalAnnounced {..} => included_htlcs += 1,
1721+
OutboundHTLCState::Committed => included_htlcs += 1,
1722+
OutboundHTLCState::RemoteRemoved {..} => included_htlcs += 1,
1723+
// We don't include AwaitingRemoteRevokeToRemove HTLCs because our next commitment
1724+
// transaction won't be generated until they send us their next RAA, which will mean
1725+
// dropping any HTLCs in this state.
17041726
_ => {},
17051727
}
17061728
}
17071729

17081730
for htlc in self.holding_cell_htlc_updates.iter() {
17091731
match htlc {
1710-
&HTLCUpdateAwaitingACK::AddHTLC { .. } => their_acked_htlcs += 1,
1711-
_ => {},
1732+
&HTLCUpdateAwaitingACK::AddHTLC { amount_msat, .. } => {
1733+
if amount_msat / 1000 < real_dust_limit_timeout_sat {
1734+
continue
1735+
}
1736+
included_htlcs += 1
1737+
},
1738+
_ => {}, // Don't include claims/fails that are awaiting ack, because once we get the
1739+
// ack we're guaranteed to never include them in commitment txs anymore.
17121740
}
17131741
}
17141742

1715-
self.commit_tx_fee_msat(their_acked_htlcs + addl_htlcs)
1743+
self.commit_tx_fee_msat(included_htlcs + addl_htlcs)
17161744
}
17171745

1718-
// Get the commitment tx fee for the remote's next commitment transaction
1719-
// based on the number of pending HTLCs that are on track to be in their
1720-
// next commitment tx. `addl_htcs` is an optional parameter allowing the caller
1721-
// to add a number of additional HTLCs to the calculation. Note that dust HTLCs
1722-
// are excluded.
1723-
fn next_remote_commit_tx_fee_msat(&self, addl_htlcs: usize) -> u64 {
1746+
// Get the commitment tx fee for the remote's next commitment transaction based on the number of
1747+
// pending HTLCs that are on track to be in their next commitment tx, plus an additional HTLC if
1748+
// `fee_spike_buffer_htlc` is Some, plus a new HTLC given by `new_htlc_amount`. Dust HTLCs are
1749+
// excluded.
1750+
fn next_remote_commit_tx_fee_msat(&self, new_htlc_amount: u64, locally_offered: bool, fee_spike_buffer_htlc: Option<()>) -> u64 {
17241751
assert!(!self.is_outbound());
17251752

1726-
// When calculating the set of HTLCs which will be included in their next
1727-
// commitment_signed, all inbound HTLCs are included (as all states imply it will be
1728-
// included) and only committed outbound HTLCs, see below.
1729-
let mut their_acked_htlcs = self.pending_inbound_htlcs.len();
1753+
let real_dust_limit_success_sat = (self.feerate_per_kw as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + self.counterparty_dust_limit_satoshis;
1754+
let real_dust_limit_timeout_sat = (self.feerate_per_kw as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + self.counterparty_dust_limit_satoshis;
1755+
1756+
let mut addl_htlcs = 0;
1757+
if fee_spike_buffer_htlc.is_some() { addl_htlcs += 1; }
1758+
if locally_offered && new_htlc_amount / 1000 >= real_dust_limit_success_sat {
1759+
addl_htlcs += 1;
1760+
} else if !locally_offered && new_htlc_amount / 1000 >= real_dust_limit_timeout_sat {
1761+
addl_htlcs += 1;
1762+
}
1763+
1764+
// When calculating the set of HTLCs which will be included in their next commitment_signed, all
1765+
// non-dust inbound HTLCs are included (as all states imply it will be included) and only
1766+
// committed outbound HTLCs, see below.
1767+
let mut included_htlcs = 0;
1768+
for ref htlc in self.pending_inbound_htlcs.iter() {
1769+
if htlc.amount_msat / 1000 <= real_dust_limit_timeout_sat {
1770+
continue
1771+
}
1772+
included_htlcs += 1;
1773+
}
1774+
17301775
for ref htlc in self.pending_outbound_htlcs.iter() {
1731-
if htlc.amount_msat / 1000 <= self.counterparty_dust_limit_satoshis {
1776+
if htlc.amount_msat / 1000 <= real_dust_limit_success_sat {
17321777
continue
17331778
}
1734-
// We only include outbound HTLCs if it will not be included in their next
1735-
// commitment_signed, i.e. if they've responded to us with an RAA after announcement.
1779+
// We only include outbound HTLCs if it will not be included in their next commitment_signed,
1780+
// i.e. if they've responded to us with an RAA after announcement.
17361781
match htlc.state {
1737-
OutboundHTLCState::Committed => their_acked_htlcs += 1,
1738-
OutboundHTLCState::RemoteRemoved {..} => their_acked_htlcs += 1,
1782+
OutboundHTLCState::Committed => included_htlcs += 1,
1783+
OutboundHTLCState::RemoteRemoved {..} => included_htlcs += 1,
17391784
_ => {},
17401785
}
17411786
}
17421787

1743-
self.commit_tx_fee_msat(their_acked_htlcs + addl_htlcs)
1788+
self.commit_tx_fee_msat(included_htlcs + addl_htlcs)
17441789
}
17451790

17461791
pub fn update_add_htlc<F, L: Deref>(&mut self, msg: &msgs::UpdateAddHTLC, mut pending_forward_status: PendingHTLCStatus, create_pending_htlc_status: F, logger: &L) -> Result<(), ChannelError>
@@ -1808,8 +1853,7 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
18081853
// Check that the remote can afford to pay for this HTLC on-chain at the current
18091854
// feerate_per_kw, while maintaining their channel reserve (as required by the spec).
18101855
let remote_commit_tx_fee_msat = if self.is_outbound() { 0 } else {
1811-
// +1 for this HTLC.
1812-
self.next_remote_commit_tx_fee_msat(1)
1856+
self.next_remote_commit_tx_fee_msat(msg.amount_msat, false, None) // Don't include the extra fee spike buffer HTLC in calculations
18131857
};
18141858
if pending_remote_value_msat - msg.amount_msat < remote_commit_tx_fee_msat {
18151859
return Err(ChannelError::Close("Remote HTLC add would not leave enough to pay for fees".to_owned()));
@@ -1822,14 +1866,15 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
18221866
}
18231867

18241868
if !self.is_outbound() {
1825-
// `+1` for this HTLC, `2 *` and `+1` fee spike buffer we keep for the remote. This deviates from the
1826-
// spec because in the spec, the fee spike buffer requirement doesn't exist on the receiver's side,
1827-
// only on the sender's.
1828-
// Note that when we eventually remove support for fee updates and switch to anchor output fees,
1829-
// we will drop the `2 *`, since we no longer be as sensitive to fee spikes. But, keep the extra +1
1830-
// as we should still be able to afford adding this HTLC plus one more future HTLC, regardless of
1831-
// being sensitive to fee spikes.
1832-
let remote_fee_cost_incl_stuck_buffer_msat = 2 * self.next_remote_commit_tx_fee_msat(1 + 1);
1869+
// `2 *` and `Some(())` is for the fee spike buffer we keep for the remote. This deviates from
1870+
// the spec because in the spec, the fee spike buffer requirement doesn't exist on the
1871+
// receiver's side, only on the sender's.
1872+
// Note that when we eventually remove support for fee updates and switch to anchor output
1873+
// fees, we will drop the `2 *`, since we no longer be as sensitive to fee spikes. But, keep
1874+
// the extra htlc when calculating the next remote commitment transaction fee as we should
1875+
// still be able to afford adding this HTLC plus one more future HTLC, regardless of being
1876+
// sensitive to fee spikes.
1877+
let remote_fee_cost_incl_stuck_buffer_msat = 2 * self.next_remote_commit_tx_fee_msat(msg.amount_msat, false, Some(()));
18331878
if pending_remote_value_msat - msg.amount_msat - chan_reserve_msat < remote_fee_cost_incl_stuck_buffer_msat {
18341879
// Note that if the pending_forward_status is not updated here, then it's because we're already failing
18351880
// the HTLC, i.e. its status is already set to failing.
@@ -1838,9 +1883,7 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
18381883
}
18391884
} else {
18401885
// Check that they won't violate our local required channel reserve by adding this HTLC.
1841-
1842-
// +1 for this HTLC.
1843-
let local_commit_tx_fee_msat = self.next_local_commit_tx_fee_msat(1);
1886+
let local_commit_tx_fee_msat = self.next_local_commit_tx_fee_msat(msg.amount_msat, false, None);
18441887
if self.value_to_self_msat < self.counterparty_selected_channel_reserve_satoshis * 1000 + local_commit_tx_fee_msat {
18451888
return Err(ChannelError::Close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned()));
18461889
}
@@ -3720,11 +3763,9 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
37203763

37213764
if !self.is_outbound() {
37223765
// Check that we won't violate the remote channel reserve by adding this HTLC.
3723-
37243766
let counterparty_balance_msat = self.channel_value_satoshis * 1000 - self.value_to_self_msat;
37253767
let holder_selected_chan_reserve_msat = Channel::<ChanSigner>::get_holder_selected_channel_reserve_satoshis(self.channel_value_satoshis);
3726-
// 1 additional HTLC corresponding to this HTLC.
3727-
let counterparty_commit_tx_fee_msat = self.next_remote_commit_tx_fee_msat(1);
3768+
let counterparty_commit_tx_fee_msat = self.next_remote_commit_tx_fee_msat(amount_msat, true, None);
37283769
if counterparty_balance_msat < holder_selected_chan_reserve_msat + counterparty_commit_tx_fee_msat {
37293770
return Err(ChannelError::Ignore("Cannot send value that would put counterparty balance under holder-announced channel reserve value".to_owned()));
37303771
}
@@ -3735,10 +3776,9 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
37353776
return Err(ChannelError::Ignore(format!("Cannot send value that would overdraw remaining funds. Amount: {}, pending value to self {}", amount_msat, pending_value_to_self_msat)));
37363777
}
37373778

3738-
// The `+1` is for the HTLC currently being added to the commitment tx and
3739-
// the `2 *` and `+1` are for the fee spike buffer.
3779+
// `2 *` and extra HTLC are for the fee spike buffer.
37403780
let commit_tx_fee_msat = if self.is_outbound() {
3741-
2 * self.next_local_commit_tx_fee_msat(1 + 1)
3781+
2 * self.next_local_commit_tx_fee_msat(amount_msat, true, Some(()))
37423782
} else { 0 };
37433783
if pending_value_to_self_msat - amount_msat < commit_tx_fee_msat {
37443784
return Err(ChannelError::Ignore(format!("Cannot send value that would not leave enough to pay for fees. Pending value to self: {}. local_commit_tx_fee {}", pending_value_to_self_msat, commit_tx_fee_msat)));
@@ -4492,7 +4532,7 @@ mod tests {
44924532
use ln::features::InitFeatures;
44934533
use ln::msgs::{OptionalField, DataLossProtect, DecodeError};
44944534
use ln::chan_utils;
4495-
use ln::chan_utils::{ChannelPublicKeys, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
4535+
use ln::chan_utils::{ChannelPublicKeys, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters, HTLC_SUCCESS_TX_WEIGHT, HTLC_TIMEOUT_TX_WEIGHT};
44964536
use chain::chaininterface::{FeeEstimator,ConfirmationTarget};
44974537
use chain::keysinterface::{InMemoryChannelKeys, KeysInterface};
44984538
use chain::transaction::OutPoint;
@@ -4575,6 +4615,116 @@ mod tests {
45754615
assert_eq!(open_channel_msg.feerate_per_kw, original_fee);
45764616
}
45774617

4618+
#[test]
4619+
fn test_holder_vs_counterparty_dust_limit() {
4620+
// Test that when calculating the local and remote commitment transaction fees, the correct
4621+
// dust limits are used.
4622+
let feeest = TestFeeEstimator{fee_est: 15000};
4623+
let secp_ctx = Secp256k1::new();
4624+
let seed = [42; 32];
4625+
let network = Network::Testnet;
4626+
let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
4627+
4628+
// Go through the flow of opening a channel between two nodes, making sure
4629+
// they have different dust limits.
4630+
4631+
// Create Node A's channel pointing to Node B's pubkey
4632+
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
4633+
let config = UserConfig::default();
4634+
let mut node_a_chan = Channel::<EnforcingChannelKeys>::new_outbound(&&feeest, &&keys_provider, node_b_node_id, 10000000, 100000, 42, &config).unwrap();
4635+
4636+
// Create Node B's channel by receiving Node A's open_channel message
4637+
// Make sure A's dust limit is as we expect.
4638+
let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
4639+
assert_eq!(open_channel_msg.dust_limit_satoshis, 1560);
4640+
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
4641+
let node_b_chan = Channel::<EnforcingChannelKeys>::new_from_req(&&feeest, &&keys_provider, node_b_node_id, InitFeatures::known(), &open_channel_msg, 7, &config).unwrap();
4642+
4643+
// Node B --> Node A: accept channel, explicitly setting B's dust limit.
4644+
let mut accept_channel_msg = node_b_chan.get_accept_channel();
4645+
accept_channel_msg.dust_limit_satoshis = 546;
4646+
node_a_chan.accept_channel(&accept_channel_msg, &config, InitFeatures::known()).unwrap();
4647+
4648+
// Put some inbound and outbound HTLCs in A's channel.
4649+
let htlc_amount_msat = 11_092_000; // put an amount below A's effective dust amount but above B's.
4650+
node_a_chan.pending_inbound_htlcs.push(InboundHTLCOutput {
4651+
htlc_id: 0,
4652+
amount_msat: htlc_amount_msat,
4653+
payment_hash: PaymentHash(Sha256::hash(&[42; 32]).into_inner()),
4654+
cltv_expiry: 300000000,
4655+
state: InboundHTLCState::Committed,
4656+
});
4657+
4658+
node_a_chan.pending_outbound_htlcs.push(OutboundHTLCOutput {
4659+
htlc_id: 1,
4660+
amount_msat: htlc_amount_msat, // put an amount below A's dust amount but above B's.
4661+
payment_hash: PaymentHash(Sha256::hash(&[43; 32]).into_inner()),
4662+
cltv_expiry: 200000000,
4663+
state: OutboundHTLCState::Committed,
4664+
source: HTLCSource::OutboundRoute {
4665+
path: Vec::new(),
4666+
session_priv: SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
4667+
first_hop_htlc_msat: 548,
4668+
}
4669+
});
4670+
4671+
// Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
4672+
// the dust limit check.
4673+
let local_commit_tx_fee = node_a_chan.next_local_commit_tx_fee_msat(htlc_amount_msat, true, None);
4674+
let local_commit_fee_0_htlcs = node_a_chan.commit_tx_fee_msat(0);
4675+
assert_eq!(local_commit_tx_fee, local_commit_fee_0_htlcs);
4676+
4677+
// Finally, make sure that when Node A calculates the remote's commitment transaction fees, all
4678+
// of the HTLCs are seen to be above the dust limit.
4679+
node_a_chan.channel_transaction_parameters.is_outbound_from_holder = false;
4680+
let remote_commit_fee_3_htlcs = node_a_chan.commit_tx_fee_msat(3);
4681+
let remote_commit_tx_fee = node_a_chan.next_remote_commit_tx_fee_msat(htlc_amount_msat, true, None);
4682+
assert_eq!(remote_commit_tx_fee, remote_commit_fee_3_htlcs);
4683+
}
4684+
4685+
#[test]
4686+
fn test_timeout_vs_success_htlc_dust_limit() {
4687+
// Make sure that when `next_remote_commit_tx_fee_msat` and `next_local_commit_tx_fee_msat`
4688+
// calculate the real dust limits for HTLCs (i.e. the dust limit given by the counterparty
4689+
// *plus* the fees paid for the HTLC) they don't swap `HTLC_SUCCESS_TX_WEIGHT` for
4690+
// `HTLC_TIMEOUT_TX_WEIGHT`, and vice versa.
4691+
let fee_est = TestFeeEstimator{fee_est: 253 };
4692+
let secp_ctx = Secp256k1::new();
4693+
let seed = [42; 32];
4694+
let network = Network::Testnet;
4695+
let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
4696+
4697+
let node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
4698+
let config = UserConfig::default();
4699+
let mut chan = Channel::<EnforcingChannelKeys>::new_outbound(&&fee_est, &&keys_provider, node_id, 10000000, 100000, 42, &config).unwrap();
4700+
4701+
let commitment_tx_fee_0_htlcs = chan.commit_tx_fee_msat(0);
4702+
let commitment_tx_fee_1_htlc = chan.commit_tx_fee_msat(1);
4703+
4704+
// If HTLC_SUCCESS_TX_WEIGHT and HTLC_TIMEOUT_TX_WEIGHT were swapped: then this HTLC would be
4705+
// counted as dust when it shouldn't be.
4706+
let htlc_amt_above_timeout = ((253 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + chan.holder_dust_limit_satoshis + 1) * 1000;
4707+
let commitment_tx_fee = chan.next_local_commit_tx_fee_msat(htlc_amt_above_timeout, true, None);
4708+
assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc);
4709+
4710+
// If swapped: this HTLC would be counted as non-dust when it shouldn't be.
4711+
let dust_htlc_amt_below_success = ((253 * HTLC_SUCCESS_TX_WEIGHT / 1000) + chan.holder_dust_limit_satoshis - 1) * 1000;
4712+
let commitment_tx_fee = chan.next_local_commit_tx_fee_msat(dust_htlc_amt_below_success, false, None);
4713+
assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs);
4714+
4715+
chan.channel_transaction_parameters.is_outbound_from_holder = false;
4716+
4717+
// If swapped: this HTLC would be counted as non-dust when it shouldn't be.
4718+
let dust_htlc_amt_above_timeout = ((253 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + chan.counterparty_dust_limit_satoshis + 1) * 1000;
4719+
let commitment_tx_fee = chan.next_remote_commit_tx_fee_msat(dust_htlc_amt_above_timeout, true, None);
4720+
assert_eq!(commitment_tx_fee, commitment_tx_fee_0_htlcs);
4721+
4722+
// If swapped: this HTLC would be counted as dust when it shouldn't be.
4723+
let htlc_amt_below_success = ((253 * HTLC_SUCCESS_TX_WEIGHT / 1000) + chan.counterparty_dust_limit_satoshis - 1) * 1000;
4724+
let commitment_tx_fee = chan.next_remote_commit_tx_fee_msat(htlc_amt_below_success, false, None);
4725+
assert_eq!(commitment_tx_fee, commitment_tx_fee_1_htlc);
4726+
}
4727+
45784728
#[test]
45794729
fn channel_reestablish_no_updates() {
45804730
let feeest = TestFeeEstimator{fee_est: 15000};

0 commit comments

Comments
 (0)