Skip to content
13 changes: 5 additions & 8 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2736,15 +2736,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}
} else {
let mut claimable_inbound_htlc_value_sat = 0;
let mut nondust_htlc_count = 0;
let mut outbound_payment_htlc_rounded_msat = 0;
let mut outbound_forwarded_htlc_rounded_msat = 0;
let mut inbound_claiming_htlc_rounded_msat = 0;
let mut inbound_htlc_rounded_msat = 0;
for (htlc, source) in holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES) {
if htlc.transaction_output_index.is_some() {
nondust_htlc_count += 1;
}
let rounded_value_msat = if htlc.transaction_output_index.is_none() {
htlc.amount_msat
} else { htlc.amount_msat % 1000 };
Expand Down Expand Up @@ -2788,11 +2784,12 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
let to_self_value_sat = us.funding.current_holder_commitment_tx.to_broadcaster_value_sat();
res.push(Balance::ClaimableOnChannelClose {
amount_satoshis: to_self_value_sat + claimable_inbound_htlc_value_sat,
// In addition to `commit_tx_fee_sat`, this can also include dust HTLCs, and the total msat amount rounded down from non-dust HTLCs
transaction_fee_satoshis: if us.holder_pays_commitment_tx_fee.unwrap_or(true) {
chan_utils::commit_tx_fee_sat(
us.funding.current_holder_commitment_tx.feerate_per_kw(), nondust_htlc_count,
us.channel_type_features(),
)
let transaction = &us.funding.current_holder_commitment_tx.trust().built_transaction().transaction;
// Unwrap here; commitment transactions always have at least one output
let output_value_sat = transaction.output.iter().map(|txout| txout.value).reduce(|sum, value| sum + value).unwrap().to_sat();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why cant we use sum? Maybe mapping to_sat in the first map?

us.funding.channel_parameters.channel_value_satoshis - output_value_sat
} else { 0 },
outbound_payment_htlc_rounded_msat,
outbound_forwarded_htlc_rounded_msat,
Expand Down
6 changes: 2 additions & 4 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,16 +234,14 @@ pub(crate) fn commit_tx_fee_sat(feerate_per_kw: u32, num_htlcs: usize, channel_t
}

#[rustfmt::skip]
pub(crate) fn commit_and_htlc_tx_fees_sat(feerate_per_kw: u32, num_accepted_htlcs: usize, num_offered_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
let num_htlcs = num_accepted_htlcs + num_offered_htlcs;
let commit_tx_fees_sat = commit_tx_fee_sat(feerate_per_kw, num_htlcs, channel_type_features);
pub(crate) fn htlc_tx_fees_sat(feerate_per_kw: u32, num_accepted_htlcs: usize, num_offered_htlcs: usize, channel_type_features: &ChannelTypeFeatures) -> u64 {
let htlc_tx_fees_sat = if !channel_type_features.supports_anchors_zero_fee_htlc_tx() {
num_accepted_htlcs as u64 * htlc_success_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
+ num_offered_htlcs as u64 * htlc_timeout_tx_weight(channel_type_features) * feerate_per_kw as u64 / 1000
} else {
0
};
commit_tx_fees_sat + htlc_tx_fees_sat
htlc_tx_fees_sat
}

// Various functions for key derivation and transaction creation for use within channels. Primarily
Expand Down
395 changes: 183 additions & 212 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions lightning/src/ln/monitor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,9 +544,11 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
let commitment_tx_fee = chan_feerate as u64 *
(chan_utils::commitment_tx_base_weight(&channel_type_features) + 2 * chan_utils::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000;
let anchor_outputs_value = if anchors { 2 * channel::ANCHOR_OUTPUT_VALUE_SATOSHI } else { 0 };
let amount_satoshis = 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - commitment_tx_fee - anchor_outputs_value - 1; /* msat amount that is burned to fees */
assert_eq!(sorted_vec(vec![Balance::ClaimableOnChannelClose {
amount_satoshis: 1_000_000 - 3_000 - 4_000 - 1_000 - 3 - commitment_tx_fee - anchor_outputs_value - 1 /* msat amount that is burned to fees */,
transaction_fee_satoshis: commitment_tx_fee,
amount_satoshis,
// In addition to `commitment_tx_fee`, this also includes the dust HTLC, and the total msat amount rounded down from non-dust HTLCs
transaction_fee_satoshis: 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to update docs.

outbound_payment_htlc_rounded_msat: 3300,
outbound_forwarded_htlc_rounded_msat: 0,
inbound_claiming_htlc_rounded_msat: 0,
Expand Down Expand Up @@ -598,16 +600,18 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
let commitment_tx_fee = chan_feerate as u64 *
(chan_utils::commitment_tx_base_weight(&channel_type_features) +
if prev_commitment_tx { 1 } else { 2 } * chan_utils::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000;
let mut a_expected_balances = vec![Balance::ClaimableOnChannelClose {
amount_satoshis: 1_000_000 - // Channel funding value in satoshis
let amount_satoshis = 1_000_000 - // Channel funding value in satoshis
4_000 - // The to-be-failed HTLC value in satoshis
3_000 - // The claimed HTLC value in satoshis
1_000 - // The push_msat value in satoshis
3 - // The dust HTLC value in satoshis
commitment_tx_fee - // The commitment transaction fee with two HTLC outputs
anchor_outputs_value - // The anchor outputs value in satoshis
1, // The rounded up msat part of the one HTLC
transaction_fee_satoshis: commitment_tx_fee,
1; // The rounded up msat part of the one HTLC
let mut a_expected_balances = vec![Balance::ClaimableOnChannelClose {
amount_satoshis, // Channel funding value in satoshis
// In addition to `commitment_tx_fee`, this also includes the dust HTLC, and the total msat amount rounded down from non-dust HTLCs
transaction_fee_satoshis: 1_000_000 - 4_000 - 3_000 - 1_000 - amount_satoshis - anchor_outputs_value,
outbound_payment_htlc_rounded_msat: 3000 + if prev_commitment_tx {
200 /* 1 to-be-failed HTLC */ } else { 300 /* 2 HTLCs */ },
outbound_forwarded_htlc_rounded_msat: 0,
Expand Down
182 changes: 182 additions & 0 deletions lightning/src/ln/update_fee_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,3 +1193,185 @@ pub fn do_cannot_afford_on_holding_cell_release(
nodes[0].logger.assert_log("lightning::ln::channel", err, 1);
}
}

#[xtest(feature = "_externalize_tests")]
pub fn can_afford_given_trimmed_htlcs() {
do_can_afford_given_trimmed_htlcs(core::cmp::Ordering::Equal);
do_can_afford_given_trimmed_htlcs(core::cmp::Ordering::Greater);
do_can_afford_given_trimmed_htlcs(core::cmp::Ordering::Less);
}

pub fn do_can_afford_given_trimmed_htlcs(inequality_regions: core::cmp::Ordering) {
// Test that when we check whether we can afford a feerate update, we account for the
// decrease in the weight of the commitment transaction due to newly trimmed HTLCs at the higher feerate.
//
// Place a non-dust HTLC on the transaction, increase the feerate such that the HTLC
// gets trimmed, and finally check whether we were able to afford the new feerate.

let channel_type = ChannelTypeFeatures::only_static_remote_key();
let can_afford = match inequality_regions {
core::cmp::Ordering::Less => false,
core::cmp::Ordering::Equal => true,
core::cmp::Ordering::Greater => true,
};
let inequality_boundary_offset = match inequality_regions {
core::cmp::Ordering::Less => 0,
core::cmp::Ordering::Equal => 1,
core::cmp::Ordering::Greater => 2,
};

let chanmon_cfgs = create_chanmon_cfgs(2);

let mut default_config = test_default_channel_config();
default_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel =
100;

let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs =
create_node_chanmgrs(2, &node_cfgs, &[Some(default_config.clone()), Some(default_config)]);

let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);

let node_a_id = nodes[0].node.get_our_node_id();
let node_b_id = nodes[1].node.get_our_node_id();

// We will update the feerate from 253sat/kw to 1000sat/kw
let target_feerate = 1000;
// Set a HTLC amount that is non-dust at 253sat/kw and dust at 1000sat/kw
let node_0_inbound_htlc_amount_sat = 750;

// This is the number of HTLCs that `can_send_update_fee` will account for when checking
// whether node 0 can afford the target feerate. We do not include the inbound HTLC we will send,
// as that HTLC will be trimmed at the new feerate.
let buffer_tx_fee_sat = chan_utils::commit_tx_fee_sat(
target_feerate,
crate::ln::channel::CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize,
&channel_type,
);
let channel_reserve_satoshis = 1000;

let channel_value_sat = 100_000;
let node_0_balance_sat =
(buffer_tx_fee_sat + channel_reserve_satoshis) - 1 + inequality_boundary_offset;
let node_1_balance_sat = channel_value_sat - node_0_balance_sat;

let chan_id =
create_chan_between_nodes_with_value(&nodes[0], &nodes[1], channel_value_sat, 0).3;
{
// Double check the reserve here
let per_peer_state_lock;
let mut peer_state_lock;
let chan =
get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, chan_id);
assert_eq!(
chan.funding().holder_selected_channel_reserve_satoshis,
channel_reserve_satoshis
);
}

// Set node 0's balance at some offset from the inequality boundary
send_payment(&nodes[0], &[&nodes[1]], node_1_balance_sat * 1000);

// Route the HTLC from node 1 to node 0
route_payment(&nodes[1], &[&nodes[0]], node_0_inbound_htlc_amount_sat * 1000);

// Confirm the feerate on node 0's commitment transaction
{
let expected_tx_fee_sat = chan_utils::commit_tx_fee_sat(253, 1, &channel_type);
let commitment_tx = get_local_commitment_txn!(nodes[0], chan_id)[0].clone();

let mut actual_fee = commitment_tx
.output
.iter()
.map(|output| output.value.to_sat())
.reduce(|acc, value| acc + value)
.unwrap();
actual_fee = channel_value_sat - actual_fee;
assert_eq!(expected_tx_fee_sat, actual_fee);

// The HTLC is non-dust...
assert_eq!(commitment_tx.output.len(), 3);
}

{
// Bump the feerate
let mut feerate_lock = chanmon_cfgs[0].fee_estimator.sat_per_kw.lock().unwrap();
*feerate_lock = target_feerate;
}
nodes[0].node.timer_tick_occurred();
check_added_monitors(&nodes[0], if can_afford { 1 } else { 0 });
let mut events = nodes[0].node.get_and_clear_pending_msg_events();

if can_afford {
// We could afford the target feerate, sanity check everything
assert_eq!(events.len(), 1);
if let MessageSendEvent::UpdateHTLCs { node_id, channel_id, updates } =
events.pop().unwrap()
{
assert_eq!(node_id, node_b_id);
assert_eq!(channel_id, chan_id);
assert_eq!(updates.commitment_signed.len(), 1);
// The HTLC is now trimmed!
assert_eq!(updates.commitment_signed[0].htlc_signatures.len(), 0);
assert_eq!(updates.update_add_htlcs.len(), 0);
assert_eq!(updates.update_fulfill_htlcs.len(), 0);
assert_eq!(updates.update_fail_htlcs.len(), 0);
assert_eq!(updates.update_fail_malformed_htlcs.len(), 0);
let update_fee = updates.update_fee.unwrap();
assert_eq!(update_fee.channel_id, chan_id);
assert_eq!(update_fee.feerate_per_kw, target_feerate);

nodes[1].node.handle_update_fee(node_a_id, &update_fee);
commitment_signed_dance!(nodes[1], nodes[0], updates.commitment_signed, false);

// Confirm the feerate on node 0's commitment transaction
{
// Also add the trimmed HTLC to the fees
let expected_tx_fee_sat =
chan_utils::commit_tx_fee_sat(target_feerate, 0, &channel_type)
+ node_0_inbound_htlc_amount_sat;
let commitment_tx = get_local_commitment_txn!(nodes[0], channel_id)[0].clone();

let mut actual_fee = commitment_tx
.output
.iter()
.map(|output| output.value.to_sat())
.reduce(|acc, value| acc + value)
.unwrap();
actual_fee = channel_value_sat - actual_fee;
assert_eq!(expected_tx_fee_sat, actual_fee);

// The HTLC is now trimmed!
assert_eq!(commitment_tx.output.len(), 2);
}

// Confirm the feerate on node 1's commitment transaction
{
// Also add the trimmed HTLC to the fees
let expected_tx_fee_sat =
chan_utils::commit_tx_fee_sat(target_feerate, 0, &channel_type)
+ node_0_inbound_htlc_amount_sat;
let commitment_tx = get_local_commitment_txn!(nodes[1], channel_id)[0].clone();

let mut actual_fee = commitment_tx
.output
.iter()
.map(|output| output.value.to_sat())
.reduce(|acc, value| acc + value)
.unwrap();
actual_fee = channel_value_sat - actual_fee;
assert_eq!(expected_tx_fee_sat, actual_fee);

// The HTLC is now trimmed!
assert_eq!(commitment_tx.output.len(), 2);
}
} else {
panic!();
}
} else {
// We could not afford the target feerate, no events should be generated
assert_eq!(events.len(), 0);
let err = format!("Cannot afford to send new feerate at {}", target_feerate);
nodes[0].logger.assert_log("lightning::ln::channel", err, 1);
}
}
1 change: 1 addition & 0 deletions lightning/src/sign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub(crate) mod type_resolver;
pub mod ecdsa;
#[cfg(taproot)]
pub mod taproot;
pub mod tx_builder;

/// Information about a spendable output to a P2WSH script.
///
Expand Down
Loading
Loading