Skip to content

[Custom Transactions] Add TxBuilder trait, support fixed additional outputs #3775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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();
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,
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