Skip to content

Commit

Permalink
[Custom Transactions] Define the TxBuilder trait
Browse files Browse the repository at this point in the history
This commit defines the `TxBuilder` trait to give users the ability to
customize the build of the lightning commitment transactions.

The `TxBuilder` trait has a single method,
`build_commitment_transaction`, which builds the commitment transaction,
and populates the output indices of the HTLCs passed in.

Remove generic from `CommitmentTransaction::new`

For the greatest compatibility with bindings, the argument is now a
`Vec<HTLCOutputInCommitment>`. These non-dust HTLCs are populated during
the construction of `CommitmentTransaction` and eventually cached by
the object. Anything interested in these populated HTLCs should now
use `CommitmentTransaction::htlcs`.

Back in channel, we choose to do a brute force search over the
HTLC-source pairs to assign their output indices. This is O(n^2) over
~1k HTLCs in the worst case. This case is very rare/non-existent at the
moment. Feel free to optimize if LN channels start seeing an increasing
number of HTLCs on their commitment transactions.
  • Loading branch information
tankyleo committed Mar 9, 2025
1 parent d6a569f commit 1ff0e41
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 193 deletions.
48 changes: 27 additions & 21 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3432,12 +3432,12 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
fn build_counterparty_commitment_tx(
&self, commitment_number: u64, their_per_commitment_point: &PublicKey,
to_broadcaster_value: u64, to_countersignatory_value: u64, feerate_per_kw: u32,
mut nondust_htlcs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>
nondust_htlcs: Vec<HTLCOutputInCommitment>,
) -> CommitmentTransaction {
let channel_parameters =
&self.onchain_tx_handler.channel_transaction_parameters.as_counterparty_broadcastable();
CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number, their_per_commitment_point,
to_broadcaster_value, to_countersignatory_value, feerate_per_kw, &mut nondust_htlcs, channel_parameters, &self.onchain_tx_handler.secp_ctx)
CommitmentTransaction::new(commitment_number, their_per_commitment_point,
to_broadcaster_value, to_countersignatory_value, feerate_per_kw, nondust_htlcs, channel_parameters, &self.onchain_tx_handler.secp_ctx)
}

fn counterparty_commitment_txs_from_update(&self, update: &ChannelMonitorUpdate) -> Vec<CommitmentTransaction> {
Expand All @@ -3450,8 +3450,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
to_countersignatory_value_sat: Some(to_countersignatory_value) } => {

let nondust_htlcs = htlc_outputs.iter().filter_map(|(htlc, _)| {
htlc.transaction_output_index.map(|_| (htlc.clone(), None))
}).collect::<Vec<_>>();
htlc.transaction_output_index.map(|_| htlc)
}).cloned().collect::<Vec<_>>();

let commitment_tx = self.build_counterparty_commitment_tx(commitment_number,
&their_per_commitment_point, to_broadcaster_value,
Expand Down Expand Up @@ -5320,21 +5320,21 @@ mod tests {
{
let mut res = Vec::new();
for (idx, preimage) in $preimages_slice.iter().enumerate() {
res.push((HTLCOutputInCommitment {
res.push(HTLCOutputInCommitment {
offered: true,
amount_msat: 0,
cltv_expiry: 0,
payment_hash: preimage.1.clone(),
transaction_output_index: Some(idx as u32),
}, ()));
});
}
res
}
}
}
macro_rules! preimages_slice_to_htlc_outputs {
($preimages_slice: expr) => {
preimages_slice_to_htlcs!($preimages_slice).into_iter().map(|(htlc, _)| (htlc, None)).collect()
preimages_slice_to_htlcs!($preimages_slice).into_iter().map(|htlc| (htlc, None)).collect()
}
}
let dummy_sig = crate::crypto::utils::sign(&secp_ctx,
Expand Down Expand Up @@ -5390,14 +5390,16 @@ mod tests {
let monitor = ChannelMonitor::new(Secp256k1::new(), keys,
Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(),
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()),
&channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(0, &mut Vec::new()),
&channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(0, Vec::new()),
best_block, dummy_key, channel_id);

let mut htlcs = preimages_slice_to_htlcs!(preimages[0..10]);
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(0, &mut htlcs);
let htlcs = preimages_slice_to_htlcs!(preimages[0..10]);
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(0, htlcs);
// These htlcs now have their output indices assigned
let htlcs = dummy_commitment_tx.htlcs().clone();

monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx.clone(),
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect());
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx,
htlcs.into_iter().map(|htlc| (htlc, Some(dummy_sig), None)).collect());
monitor.provide_latest_counterparty_commitment_tx(Txid::from_byte_array(Sha256::hash(b"1").to_byte_array()),
preimages_slice_to_htlc_outputs!(preimages[5..15]), 281474976710655, dummy_key, &logger);
monitor.provide_latest_counterparty_commitment_tx(Txid::from_byte_array(Sha256::hash(b"2").to_byte_array()),
Expand Down Expand Up @@ -5432,21 +5434,25 @@ mod tests {

// Now update holder commitment tx info, pruning only element 18 as we still care about the
// previous commitment tx's preimages too
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..5]);
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(0, &mut htlcs);
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx.clone(),
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect());
let htlcs = preimages_slice_to_htlcs!(preimages[0..5]);
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(0, htlcs);
// These htlcs now have their output indices assigned
let htlcs = dummy_commitment_tx.htlcs().clone();
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx,
htlcs.into_iter().map(|htlc| (htlc, Some(dummy_sig), None)).collect());
secret[0..32].clone_from_slice(&<Vec<u8>>::from_hex("2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8").unwrap());
monitor.provide_secret(281474976710653, secret.clone()).unwrap();
assert_eq!(monitor.inner.lock().unwrap().payment_preimages.len(), 12);
test_preimages_exist!(&preimages[0..10], monitor);
test_preimages_exist!(&preimages[18..20], monitor);

// But if we do it again, we'll prune 5-10
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..3]);
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(0, &mut htlcs);
let htlcs = preimages_slice_to_htlcs!(preimages[0..3]);
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(0, htlcs);
// These htlcs now have their output indices assigned
let htlcs = dummy_commitment_tx.htlcs().clone();
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx,
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect());
htlcs.into_iter().map(|htlc| (htlc, Some(dummy_sig), None)).collect());
secret[0..32].clone_from_slice(&<Vec<u8>>::from_hex("27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116").unwrap());
monitor.provide_secret(281474976710652, secret.clone()).unwrap();
assert_eq!(monitor.inner.lock().unwrap().payment_preimages.len(), 5);
Expand Down Expand Up @@ -5641,7 +5647,7 @@ mod tests {
let monitor = ChannelMonitor::new(Secp256k1::new(), keys,
Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(),
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()),
&channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(0, &mut Vec::new()),
&channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(0, Vec::new()),
best_block, dummy_key, channel_id);

let chan_id = monitor.inner.lock().unwrap().channel_id();
Expand Down
11 changes: 6 additions & 5 deletions lightning/src/chain/onchaintx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1363,18 +1363,19 @@ mod tests {
for i in 0..3 {
let preimage = PaymentPreimage([i; 32]);
let hash = PaymentHash(Sha256::hash(&preimage.0[..]).to_byte_array());
htlcs.push((
htlcs.push(
HTLCOutputInCommitment {
offered: true,
amount_msat: 10000,
cltv_expiry: i as u32,
payment_hash: hash,
transaction_output_index: Some(i as u32),
},
(),
));
);
}
let holder_commit = HolderCommitmentTransaction::dummy(1000000, &mut htlcs);
let holder_commit = HolderCommitmentTransaction::dummy(1000000, htlcs);
// These htlcs now have their output indices assigned
let htlcs = holder_commit.htlcs().clone();
let mut tx_handler = OnchainTxHandler::new(
1000000,
[0; 32],
Expand All @@ -1400,7 +1401,7 @@ mod tests {
// Request claiming of each HTLC on the holder's commitment, with current block height 1.
let holder_commit_txid = tx_handler.get_unsigned_holder_commitment_tx().compute_txid();
let mut requests = Vec::new();
for (htlc, _) in htlcs {
for htlc in htlcs {
requests.push(PackageTemplate::build_package(
holder_commit_txid,
htlc.transaction_output_index.unwrap(),
Expand Down
Loading

0 comments on commit 1ff0e41

Please sign in to comment.