Skip to content

Commit 2452705

Browse files
committed
Remove a clone of all the non-dust htlcs when rebuilding a commitment tx
We leverage the `Borrow` trait to allow passing both exclusive and shared references to `CommitmentTransaction::internal_build_outputs`. This allows us to avoid cloning all the htlcs when a `CommitmentTransaction::verify` call recreates the `BuiltCommitmentTransaction`. The iterator trait used in `CommitmentTransaction::internal_build_outputs` allows us to handle both collections of pointers, and a pointer to a collection without requiring any memory allocations. We also add extensive documentation of the inner mechanics of `CommitmentTransaction`.
1 parent d2cb2df commit 2452705

File tree

1 file changed

+64
-34
lines changed

1 file changed

+64
-34
lines changed

lightning/src/ln/chan_utils.rs

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature, Message};
4040
use bitcoin::{secp256k1, Sequence, Witness};
4141

4242
use crate::io;
43+
use core::borrow::Borrow;
4344
use core::cmp;
4445
use crate::util::transaction_utils::sort_outputs;
4546
use crate::ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI};
@@ -1480,7 +1481,7 @@ impl CommitmentTransaction {
14801481
let keys = TxCreationKeys::from_channel_static_keys(per_commitment_point, channel_parameters.broadcaster_pubkeys(), channel_parameters.countersignatory_pubkeys(), secp_ctx);
14811482

14821483
// Sort outputs and populate output indices
1483-
let (outputs, nondust_htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, nondust_htlcs, channel_parameters).unwrap();
1484+
let (outputs, nondust_htlcs) = Self::build_outputs_and_htlcs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, nondust_htlcs, channel_parameters).unwrap();
14841485

14851486
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters);
14861487
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
@@ -1509,11 +1510,17 @@ impl CommitmentTransaction {
15091510
self
15101511
}
15111512

1513+
// This function
1514+
// - Builds the inputs of the commitment transaction.
1515+
// - Calls `internal_build_outputs` to get a `Vec<(TxOut, Option<&HTLCOutputInCommitment>)>` sorted according to the LN specification,
1516+
// and as they'd appear on the commitment transaction.
1517+
// - Maps this vector to a `Vec<TxOut>`, keeping the original order of the outputs.
1518+
// - Then uses the vectors of inputs and outputs to build the commitment transaction.
15121519
fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters) -> Result<BuiltCommitmentTransaction, ()> {
15131520
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters);
15141521

1515-
let mut nondust_htlcs = self.htlcs.clone();
1516-
let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, nondust_htlcs.iter_mut().collect(), channel_parameters)?;
1522+
let txout_htlc_pairs = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, self.htlcs().iter(), channel_parameters)?;
1523+
let outputs = txout_htlc_pairs.into_iter().map(|(output, _)| output).collect();
15171524

15181525
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
15191526
let txid = transaction.compute_txid();
@@ -1533,17 +1540,58 @@ impl CommitmentTransaction {
15331540
}
15341541
}
15351542

1536-
// This is used in two cases:
1537-
// - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the
1538-
// caller needs to have sorted together with the HTLCs so it can keep track of the output index
1539-
// - building of a bitcoin transaction during a verify() call, in which case T is just ()
1540-
fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, nondust_htlcs: Vec<&mut HTLCOutputInCommitment>, channel_parameters: &DirectedChannelTransactionParameters) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
1543+
// This function
1544+
// - Calls `internal_build_outputs` to get a `Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)>` sorted according to the LN specification,
1545+
// and as they'd appear on the commitment transaction.
1546+
// - Then iterates over this vector of tuples:
1547+
// - populates *all* the output indices of the exclusive references in `nondust_htlcs`,
1548+
// - creates a vector of `HTLCOutputInCommitment` sorted by their output index,
1549+
// - creates a vector of `TxOut`, sorted according to the LN Spec, see `sort_outputs`.
1550+
// - Returns the two vectors: the vector of `TxOut`, and the vector of `HTLCOutputInCommitment`:
1551+
// - `Vec<TxOut>` gets moved to the `Transaction` cached by `CommitmentTransaction`,
1552+
// - `Vec<HTLCOutputInCommitment>` gets moved to and cached by `CommitmentTransaction`.
1553+
fn build_outputs_and_htlcs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, nondust_htlcs: Vec<&mut HTLCOutputInCommitment>, channel_parameters: &DirectedChannelTransactionParameters) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
1554+
let mut htlcs: Vec<HTLCOutputInCommitment> = Vec::with_capacity(nondust_htlcs.len());
1555+
let mut txout_htlc_pairs: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Self::internal_build_outputs(keys, to_broadcaster_value_sat, to_countersignatory_value_sat, nondust_htlcs.into_iter(), channel_parameters)?;
1556+
let mut outputs: Vec<TxOut> = Vec::with_capacity(txout_htlc_pairs.len());
1557+
for (idx, out) in txout_htlc_pairs.drain(..).enumerate() {
1558+
if let Some(htlc) = out.1 {
1559+
// Remember `htlc` is a `&mut HTLCOutputInCommitment`, so this populates the HTLC exclusive references passed to `CommitmentTransaction::new`
1560+
htlc.transaction_output_index = Some(idx as u32);
1561+
// We also clone the htlcs to cache them in `CommitmentTransaction`
1562+
htlcs.push(htlc.clone());
1563+
}
1564+
outputs.push(out.0);
1565+
}
1566+
Ok((outputs, htlcs))
1567+
}
1568+
1569+
// This function
1570+
// - Takes an iterator that yields objects of type `T` that can yield a `&HTLCOutputInCommitment`; `T: Borrow<HTLCOutputInCommitment>`.
1571+
// For our use-cases, these are `&mut HTLCOutputInCommitment` (used by `CommitmentTransaction::new`), and `&HTLCOutputInCommitment` (used by `CommitmentTransaction::internal_rebuild_transaction`).
1572+
// - Builds a `Vec<(TxOut, Option<T>)>`, containing all the outputs of the commitment transaction.
1573+
// For the `Option` in the tuple, an HTLC output sets the option to `Some(T)`, and all the other outputs set the option to `None`.
1574+
// - Passes this vector to `sort_outputs` to sort all the tuples in the vector according to the LN specification.
1575+
// - Then returns this sorted vector to the caller.
1576+
fn internal_build_outputs<T: Borrow<HTLCOutputInCommitment>>(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, nondust_htlcs: impl Iterator<Item = T>, channel_parameters: &DirectedChannelTransactionParameters) -> Result<Vec<(TxOut, Option<T>)>, ()> {
15411577
let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
15421578
let broadcaster_funding_key = &channel_parameters.broadcaster_pubkeys().funding_pubkey;
15431579
let countersignatory_funding_key = &countersignatory_pubkeys.funding_pubkey;
15441580
let contest_delay = channel_parameters.contest_delay();
15451581

1546-
let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new();
1582+
// Assumes the max number of outputs on the transaction is equal to n_htlcs + to_local + to_remote + local_anchor + remote_anchor
1583+
let mut txouts: Vec<(TxOut, Option<T>)> = Vec::with_capacity(nondust_htlcs.size_hint().0 + 4);
1584+
1585+
for htlc in nondust_htlcs {
1586+
let script = get_htlc_redeemscript(htlc.borrow(), &channel_parameters.channel_type_features(), &keys);
1587+
let txout = TxOut {
1588+
script_pubkey: script.to_p2wsh(),
1589+
value: htlc.borrow().to_bitcoin_amount(),
1590+
};
1591+
txouts.push((txout, Some(htlc)));
1592+
}
1593+
// We avoid relying on `ExactSizeIterator` here
1594+
let nondust_htlcs_is_empty = txouts.is_empty();
15471595

15481596
if to_countersignatory_value_sat > Amount::ZERO {
15491597
let script = if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
@@ -1576,7 +1624,7 @@ impl CommitmentTransaction {
15761624
}
15771625

15781626
if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
1579-
if to_broadcaster_value_sat > Amount::ZERO || !nondust_htlcs.is_empty() {
1627+
if to_broadcaster_value_sat > Amount::ZERO || !nondust_htlcs_is_empty {
15801628
let anchor_script = get_anchor_redeemscript(broadcaster_funding_key);
15811629
txouts.push((
15821630
TxOut {
@@ -1587,7 +1635,7 @@ impl CommitmentTransaction {
15871635
));
15881636
}
15891637

1590-
if to_countersignatory_value_sat > Amount::ZERO || !nondust_htlcs.is_empty() {
1638+
if to_countersignatory_value_sat > Amount::ZERO || !nondust_htlcs_is_empty {
15911639
let anchor_script = get_anchor_redeemscript(countersignatory_funding_key);
15921640
txouts.push((
15931641
TxOut {
@@ -1599,41 +1647,23 @@ impl CommitmentTransaction {
15991647
}
16001648
}
16011649

1602-
let mut htlcs = Vec::with_capacity(nondust_htlcs.len());
1603-
for htlc in nondust_htlcs {
1604-
let script = get_htlc_redeemscript(&htlc, &channel_parameters.channel_type_features(), &keys);
1605-
let txout = TxOut {
1606-
script_pubkey: script.to_p2wsh(),
1607-
value: htlc.to_bitcoin_amount(),
1608-
};
1609-
txouts.push((txout, Some(htlc)));
1610-
}
1611-
16121650
// Sort output in BIP-69 order (amount, scriptPubkey). Tie-breaks based on HTLC
16131651
// CLTV expiration height.
16141652
sort_outputs(&mut txouts, |a, b| {
1615-
if let &Some(ref a_htlcout) = a {
1616-
if let &Some(ref b_htlcout) = b {
1617-
a_htlcout.cltv_expiry.cmp(&b_htlcout.cltv_expiry)
1653+
if let Some(a_htlcout) = a {
1654+
if let Some(b_htlcout) = b {
1655+
a_htlcout.borrow().cltv_expiry.cmp(&b_htlcout.borrow().cltv_expiry)
16181656
// Note that due to hash collisions, we have to have a fallback comparison
16191657
// here for fuzzing mode (otherwise at least chanmon_fail_consistency
16201658
// may fail)!
1621-
.then(a_htlcout.payment_hash.0.cmp(&b_htlcout.payment_hash.0))
1659+
.then(a_htlcout.borrow().payment_hash.0.cmp(&b_htlcout.borrow().payment_hash.0))
16221660
// For non-HTLC outputs, if they're copying our SPK we don't really care if we
16231661
// close the channel due to mismatches - they're doing something dumb:
16241662
} else { cmp::Ordering::Equal }
16251663
} else { cmp::Ordering::Equal }
16261664
});
16271665

1628-
let mut outputs = Vec::with_capacity(txouts.len());
1629-
for (idx, out) in txouts.drain(..).enumerate() {
1630-
if let Some(htlc) = out.1 {
1631-
htlc.transaction_output_index = Some(idx as u32);
1632-
htlcs.push(htlc.clone());
1633-
}
1634-
outputs.push(out.0);
1635-
}
1636-
Ok((outputs, htlcs))
1666+
Ok(txouts)
16371667
}
16381668

16391669
fn internal_build_inputs(commitment_number: u64, channel_parameters: &DirectedChannelTransactionParameters) -> (u64, Vec<TxIn>) {

0 commit comments

Comments
 (0)