Skip to content

Commit

Permalink
new tx builder method
Browse files Browse the repository at this point in the history
  • Loading branch information
tankyleo committed Feb 22, 2025
1 parent c562911 commit 3361edc
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 134 deletions.
4 changes: 3 additions & 1 deletion lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3464,9 +3464,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
let channel_parameters =
&self.onchain_tx_handler.channel_transaction_parameters.as_counterparty_broadcastable();

let nondust_htlcs = nondust_htlcs.iter_mut().map(|(htlc, _)| htlc).collect();

CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
to_broadcaster_value, to_countersignatory_value, broadcaster_funding_key,
countersignatory_funding_key, keys, feerate_per_kw, &mut nondust_htlcs,
countersignatory_funding_key, keys, feerate_per_kw, nondust_htlcs,
channel_parameters)
}

Expand Down
22 changes: 12 additions & 10 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,8 @@ impl HolderCommitmentTransaction {
for _ in 0..htlcs.len() {
counterparty_htlc_sigs.push(dummy_sig);
}
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
let htlc_muts = htlcs.iter_mut().map(|(htlc, _)| htlc).collect();
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlc_muts, &channel_parameters.as_counterparty_broadcastable());
htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index);
HolderCommitmentTransaction {
inner,
Expand Down Expand Up @@ -1445,7 +1446,7 @@ impl CommitmentTransaction {
/// Only include HTLCs that are above the dust limit for the channel.
///
/// This is not exported to bindings users due to the generic though we likely should expose a version without
pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
pub fn new_with_auxiliary_htlc_data(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: Vec<&mut HTLCOutputInCommitment>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
let to_broadcaster_value_sat = Amount::from_sat(to_broadcaster_value_sat);
let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat);

Expand Down Expand Up @@ -1482,8 +1483,9 @@ impl CommitmentTransaction {
fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<BuiltCommitmentTransaction, ()> {
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters);

let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect();
let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;
let mut htlcs_with_aux: Vec<_> = self.htlcs.iter().map(|h| h.clone()).collect();
let htlcs_with_aux = htlcs_with_aux.iter_mut().collect();
let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;

let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
let txid = transaction.compute_txid();
Expand All @@ -1507,7 +1509,7 @@ impl CommitmentTransaction {
// - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the
// caller needs to have sorted together with the HTLCs so it can keep track of the output index
// - building of a bitcoin transaction during a verify() call, in which case T is just ()
fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: Vec<&mut HTLCOutputInCommitment>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
let contest_delay = channel_parameters.contest_delay();

Expand Down Expand Up @@ -1568,7 +1570,7 @@ impl CommitmentTransaction {
}

let mut htlcs = Vec::with_capacity(htlcs_with_aux.len());
for (htlc, _) in htlcs_with_aux {
for htlc in htlcs_with_aux {
let script = get_htlc_redeemscript(&htlc, &channel_parameters.channel_type_features(), &keys);
let txout = TxOut {
script_pubkey: script.to_p2wsh(),
Expand Down Expand Up @@ -1914,7 +1916,7 @@ mod tests {
counterparty_funding_pubkey: PublicKey,
keys: TxCreationKeys,
feerate_per_kw: u32,
htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>,
htlcs_with_aux: Vec<HTLCOutputInCommitment>,
channel_parameters: ChannelTransactionParameters,
counterparty_pubkeys: ChannelPublicKeys,
}
Expand Down Expand Up @@ -1962,7 +1964,7 @@ mod tests {
self.holder_funding_pubkey.clone(),
self.counterparty_funding_pubkey.clone(),
self.keys.clone(), self.feerate_per_kw,
&mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable()
self.htlcs_with_aux.iter_mut().collect(), &self.channel_parameters.as_holder_broadcastable()
)
}
}
Expand Down Expand Up @@ -2008,7 +2010,7 @@ mod tests {

// Generate broadcaster output and received and offered HTLC outputs, w/o anchors
builder.channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
builder.htlcs_with_aux = vec![received_htlc.clone(), offered_htlc.clone()];
let tx = builder.build(3000, 0);
let keys = &builder.keys.clone();
assert_eq!(tx.built.transaction.output.len(), 3);
Expand All @@ -2021,7 +2023,7 @@ mod tests {

// Generate broadcaster output and received and offered HTLC outputs, with anchors
builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
builder.htlcs_with_aux = vec![received_htlc.clone(), offered_htlc.clone()];
let tx = builder.build(3000, 0);
assert_eq!(tx.built.transaction.output.len(), 5);
assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_p2wsh());
Expand Down
127 changes: 20 additions & 107 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3288,13 +3288,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
fn build_commitment_transaction<L: Deref>(&self, commitment_number: u64, keys: &TxCreationKeys, local: bool, generated_by_local: bool, logger: &L) -> CommitmentStats
where L::Target: Logger
{
let mut included_dust_htlcs: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::new();
let num_htlcs = self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len();
let mut included_non_dust_htlcs: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(num_htlcs);

let broadcaster_dust_limit_satoshis = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis };
let mut remote_htlc_total_msat = 0;
let mut local_htlc_total_msat = 0;
let mut value_to_self_msat_offset = 0;

let mut feerate_per_kw = self.feerate_per_kw;
Expand Down Expand Up @@ -3401,119 +3395,38 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
}
}
}
let mut value_to_self_msat: i64 = self.value_to_self_msat as i64 + value_to_self_msat_offset;
let value_to_self_msat: i64 = self.value_to_self_msat as i64 + value_to_self_msat_offset;

for (htlc, source) in htlcs_in_tx {
if htlc.offered { // "offered HTLC output"
if local {
local_htlc_total_msat += htlc.amount_msat;
} else {
remote_htlc_total_msat += htlc.amount_msat
}
let htlc_in_tx = get_htlc_in_commitment!(htlc, true);
let htlc_tx_fee = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
feerate_per_kw as u64 * htlc_timeout_tx_weight(self.get_channel_type()) / 1000
};
if htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
included_non_dust_htlcs.push((htlc_in_tx, source));
} else {
included_dust_htlcs.push((htlc_in_tx, source));
}
} else {
if local {
remote_htlc_total_msat += htlc.amount_msat
} else {
local_htlc_total_msat += htlc.amount_msat;
}
let htlc_in_tx = get_htlc_in_commitment!(htlc, false);
let htlc_tx_fee = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
0
} else {
feerate_per_kw as u64 * htlc_success_tx_weight(self.get_channel_type()) / 1000
};
if htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
included_non_dust_htlcs.push((htlc_in_tx, source));
} else {
included_dust_htlcs.push((htlc_in_tx, source));
}
}
}

let mut value_to_remote_msat: i64 = (self.channel_value_satoshis * 1000) as i64 - value_to_self_msat;
value_to_self_msat -= local_htlc_total_msat as i64;
value_to_remote_msat -= remote_htlc_total_msat as i64;
let channel_parameters = if local { self.channel_transaction_parameters.as_holder_broadcastable() }
else { self.channel_transaction_parameters.as_counterparty_broadcastable() };

#[cfg(debug_assertions)]
{
// Make sure that the to_self/to_remote is always either past the appropriate
// channel_reserve *or* it is making progress towards it.
let mut broadcaster_max_commitment_tx_output = if generated_by_local {
self.holder_max_commitment_tx_output.lock().unwrap()
} else {
self.counterparty_max_commitment_tx_output.lock().unwrap()
};
debug_assert!(broadcaster_max_commitment_tx_output.0 <= value_to_self_msat as u64 || value_to_self_msat / 1000 >= self.counterparty_selected_channel_reserve_satoshis.unwrap() as i64);
broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, value_to_self_msat as u64);
debug_assert!(broadcaster_max_commitment_tx_output.1 <= value_to_remote_msat as u64 || value_to_remote_msat / 1000 >= self.holder_selected_channel_reserve_satoshis as i64);
broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, value_to_remote_msat as u64);
}

let total_fee_sat = commit_tx_fee_sat(feerate_per_kw, included_non_dust_htlcs.len(), &self.channel_transaction_parameters.channel_type_features);
let anchors_val = if self.channel_transaction_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 } as i64;
let (value_to_self, value_to_remote) = if self.is_outbound() {
(value_to_self_msat / 1000 - anchors_val - total_fee_sat as i64, value_to_remote_msat / 1000)
} else {
(value_to_self_msat / 1000, value_to_remote_msat / 1000 - anchors_val - total_fee_sat as i64)
};
use crate::sign::tx_builder::SpecTxBuilder;
use crate::sign::tx_builder::TxBuilder;
let tx_builder = SpecTxBuilder {};
let htlcs = htlcs_in_tx.iter_mut().map(|(htlc, _)| htlc).collect();

let mut value_to_a = if local { value_to_self } else { value_to_remote };
let mut value_to_b = if local { value_to_remote } else { value_to_self };
let (funding_pubkey_a, funding_pubkey_b) = if local {
(self.get_holder_pubkeys().funding_pubkey, self.get_counterparty_pubkeys().funding_pubkey)
} else {
(self.get_counterparty_pubkeys().funding_pubkey, self.get_holder_pubkeys().funding_pubkey)
};
let (tx, total_fee_sat, num_nondust_htlcs, value_to_self_msat, value_to_remote_msat) = tx_builder.build_commitment_transaction(local, commitment_number, value_to_self_msat, self.channel_value_satoshis, self.is_outbound(), htlcs, &self.secp_ctx, feerate_per_kw, broadcaster_dust_limit_satoshis, keys, &channel_parameters);

if value_to_a >= (broadcaster_dust_limit_satoshis as i64) {
log_trace!(logger, " ...including {} output with value {}", if local { "to_local" } else { "to_remote" }, value_to_a);
} else {
value_to_a = 0;
}

if value_to_b >= (broadcaster_dust_limit_satoshis as i64) {
log_trace!(logger, " ...including {} output with value {}", if local { "to_remote" } else { "to_local" }, value_to_b);
} else {
value_to_b = 0;
}

let num_nondust_htlcs = included_non_dust_htlcs.len();

let channel_parameters =
if local { self.channel_transaction_parameters.as_holder_broadcastable() }
else { self.channel_transaction_parameters.as_counterparty_broadcastable() };
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
value_to_a as u64,
value_to_b as u64,
funding_pubkey_a,
funding_pubkey_b,
keys.clone(),
feerate_per_kw,
&mut included_non_dust_htlcs,
&channel_parameters
);
let mut htlcs_included = included_non_dust_htlcs;
// The unwrap is safe, because all non-dust HTLCs have been assigned an output index
htlcs_included.sort_unstable_by_key(|h| h.0.transaction_output_index.unwrap());
htlcs_included.append(&mut included_dust_htlcs);
let mut trimmed_htlcs = Vec::new();
let mut htlcs_on_tx = Vec::new();
for pair in htlcs_in_tx {
if pair.0.transaction_output_index.is_some() {
htlcs_on_tx.push(pair);
} else {
trimmed_htlcs.push(pair);
}
}
htlcs_on_tx.sort_unstable_by_key(|h| h.0.transaction_output_index.unwrap());
let all_htlcs: Vec<_> = htlcs_on_tx.into_iter().chain(trimmed_htlcs).collect();

CommitmentStats {
tx,
feerate_per_kw,
total_fee_sat,
num_nondust_htlcs,
htlcs_included,
htlcs_included: all_htlcs,
local_balance_msat: value_to_self_msat as u64,
remote_balance_msat: value_to_remote_msat as u64,
inbound_htlc_preimages,
Expand Down
8 changes: 4 additions & 4 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -761,15 +761,15 @@ fn test_update_fee_that_funder_cannot_afford() {
let local_chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap();
let local_chan = local_chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap();
let local_chan_signer = local_chan.get_signer();
let mut htlcs: Vec<(HTLCOutputInCommitment, ())> = vec![];
let mut htlcs: Vec<HTLCOutputInCommitment> = vec![];
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
INITIAL_COMMITMENT_NUMBER - 1,
push_sats,
channel_value - push_sats - commit_tx_fee_msat(non_buffer_feerate + 4, 0, &channel_type_features) / 1000,
local_funding, remote_funding,
commit_tx_keys.clone(),
non_buffer_feerate + 4,
&mut htlcs,
htlcs.iter_mut().collect(),
&local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable()
);
local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), Vec::new(), &secp_ctx).unwrap()
Expand Down Expand Up @@ -1490,7 +1490,7 @@ fn test_fee_spike_violation_fails_htlc() {
// signature for the commitment_signed message.
let local_chan_balance = 1313;

let accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
let mut accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
offered: false,
amount_msat: 3460001,
cltv_expiry: htlc_cltv,
Expand All @@ -1512,7 +1512,7 @@ fn test_fee_spike_violation_fails_htlc() {
local_funding, remote_funding,
commit_tx_keys.clone(),
feerate_per_kw,
&mut vec![(accepted_htlc_info, ())],
vec![&mut accepted_htlc_info],
&local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable()
);
local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), Vec::new(), &secp_ctx).unwrap()
Expand Down
Loading

0 comments on commit 3361edc

Please sign in to comment.