Skip to content

Commit cce97d5

Browse files
author
Antoine Riard
committed
Move HTLC tx generation in OnchainTxHandler
HTLC Transaction can't be bumped without sighash changes so their gneeration is one-time for nwo. We move them in OnchainTxHandler for simplifying ChannelMonitor and to prepare storage of keys material behind one external signer interface. Some tests break due to change in transaction broadcaster order. Number of transactions may vary because of temporary anti-duplicata tweak can't dissociate between 2- broadcast from different origins (ChannelMonitor, ChannelManager) and 2-broadcast from same component.
1 parent 6d79078 commit cce97d5

File tree

3 files changed

+65
-83
lines changed

3 files changed

+65
-83
lines changed

lightning/src/ln/channelmonitor.rs

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,6 @@ pub(crate) enum InputMaterial {
439439
locktime: u32,
440440
},
441441
LocalHTLC {
442-
witness_script: Script,
443-
sigs: (Signature, Signature),
444442
preimage: Option<PaymentPreimage>,
445443
amount: u64,
446444
},
@@ -468,11 +466,8 @@ impl Writeable for InputMaterial {
468466
writer.write_all(&byte_utils::be64_to_array(*amount))?;
469467
writer.write_all(&byte_utils::be32_to_array(*locktime))?;
470468
},
471-
&InputMaterial::LocalHTLC { ref witness_script, ref sigs, ref preimage, ref amount } => {
469+
&InputMaterial::LocalHTLC { ref preimage, ref amount } => {
472470
writer.write_all(&[2; 1])?;
473-
witness_script.write(writer)?;
474-
sigs.0.write(writer)?;
475-
sigs.1.write(writer)?;
476471
preimage.write(writer)?;
477472
writer.write_all(&byte_utils::be64_to_array(*amount))?;
478473
},
@@ -517,16 +512,11 @@ impl Readable for InputMaterial {
517512
}
518513
},
519514
2 => {
520-
let witness_script = Readable::read(reader)?;
521-
let their_sig = Readable::read(reader)?;
522-
let our_sig = Readable::read(reader)?;
523515
let preimage = Readable::read(reader)?;
524516
let amount = Readable::read(reader)?;
525517
InputMaterial::LocalHTLC {
526-
witness_script,
527-
sigs: (their_sig, our_sig),
528518
preimage,
529-
amount
519+
amount,
530520
}
531521
},
532522
3 => {
@@ -1680,49 +1670,32 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16801670
(claimable_outpoints, Some((htlc_txid, tx.output.clone())))
16811671
}
16821672

1683-
fn broadcast_by_local_state(&self, commitment_tx: &Transaction, local_tx: &LocalSignedTx) -> (Vec<Transaction>, Vec<TxOut>, Option<(Script, SecretKey, Script)>) {
1684-
let mut res = Vec::with_capacity(local_tx.htlc_outputs.len());
1673+
fn broadcast_by_local_state(&self, commitment_tx: &Transaction, local_tx: &LocalSignedTx) -> (Vec<ClaimRequest>, Vec<TxOut>, Option<(Script, SecretKey, Script)>) {
1674+
let mut claim_requests = Vec::with_capacity(local_tx.htlc_outputs.len());
16851675
let mut watch_outputs = Vec::with_capacity(local_tx.htlc_outputs.len());
16861676

16871677
let redeemscript = chan_utils::get_revokeable_redeemscript(&local_tx.revocation_key, self.their_to_self_delay.unwrap(), &local_tx.delayed_payment_key);
16881678
let broadcasted_local_revokable_script = if let Ok(local_delayedkey) = chan_utils::derive_private_key(&self.secp_ctx, &local_tx.per_commitment_point, self.onchain_detection.keys.delayed_payment_base_key()) {
16891679
Some((redeemscript.to_v0_p2wsh(), local_delayedkey, redeemscript))
16901680
} else { None };
16911681

1692-
for &(ref htlc, ref sigs, _) in local_tx.htlc_outputs.iter() {
1682+
for &(ref htlc, _, _) in local_tx.htlc_outputs.iter() {
16931683
if let Some(transaction_output_index) = htlc.transaction_output_index {
1694-
if let &Some(ref their_sig) = sigs {
1695-
if htlc.offered {
1696-
log_trace!(self, "Broadcasting HTLC-Timeout transaction against local commitment transactions");
1697-
let mut htlc_timeout_tx = chan_utils::build_htlc_transaction(&local_tx.txid, local_tx.feerate_per_kw, self.their_to_self_delay.unwrap(), htlc, &local_tx.delayed_payment_key, &local_tx.revocation_key);
1698-
self.onchain_detection.keys.sign_htlc_transaction(&mut htlc_timeout_tx, their_sig, &None, htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key, &local_tx.per_commitment_point, &self.secp_ctx);
1699-
1700-
log_trace!(self, "Outpoint {}:{} is being being claimed", htlc_timeout_tx.input[0].previous_output.vout, htlc_timeout_tx.input[0].previous_output.txid);
1701-
res.push(htlc_timeout_tx);
1702-
} else {
1703-
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
1704-
log_trace!(self, "Broadcasting HTLC-Success transaction against local commitment transactions");
1705-
let mut htlc_success_tx = chan_utils::build_htlc_transaction(&local_tx.txid, local_tx.feerate_per_kw, self.their_to_self_delay.unwrap(), htlc, &local_tx.delayed_payment_key, &local_tx.revocation_key);
1706-
self.onchain_detection.keys.sign_htlc_transaction(&mut htlc_success_tx, their_sig, &Some(*payment_preimage), htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key, &local_tx.per_commitment_point, &self.secp_ctx);
1707-
1708-
log_trace!(self, "Outpoint {}:{} is being being claimed", htlc_success_tx.input[0].previous_output.vout, htlc_success_tx.input[0].previous_output.txid);
1709-
res.push(htlc_success_tx);
1710-
}
1711-
}
1712-
watch_outputs.push(commitment_tx.output[transaction_output_index as usize].clone());
1713-
} else { panic!("Should have sigs for non-dust local tx outputs!") }
1684+
let preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.payment_hash) { Some(*preimage) } else { None };
1685+
claim_requests.push(ClaimRequest { absolute_timelock: ::std::u32::MAX, aggregable: false, outpoint: BitcoinOutPoint { txid: local_tx.txid, vout: transaction_output_index as u32 }, witness_data: InputMaterial::LocalHTLC { preimage, amount: htlc.amount_msat / 1000 }});
1686+
watch_outputs.push(commitment_tx.output[transaction_output_index as usize].clone());
17141687
}
17151688
}
17161689

1717-
(res, watch_outputs, broadcasted_local_revokable_script)
1690+
(claim_requests, watch_outputs, broadcasted_local_revokable_script)
17181691
}
17191692

17201693
/// Attempts to claim any claimable HTLCs in a commitment transaction which was not (yet)
17211694
/// revoked using data in local_claimable_outpoints.
17221695
/// Should not be used if check_spend_revoked_transaction succeeds.
1723-
fn check_spend_local_transaction(&mut self, tx: &Transaction, height: u32) -> (Vec<Transaction>, (Sha256dHash, Vec<TxOut>)) {
1696+
fn check_spend_local_transaction(&mut self, tx: &Transaction, height: u32) -> (Vec<ClaimRequest>, (Sha256dHash, Vec<TxOut>)) {
17241697
let commitment_txid = tx.txid();
1725-
let mut local_txn = Vec::new();
1698+
let mut claim_requests = Vec::new();
17261699
let mut watch_outputs = Vec::new();
17271700

17281701
macro_rules! wait_threshold_conf {
@@ -1750,7 +1723,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
17501723

17511724
macro_rules! append_onchain_update {
17521725
($updates: expr) => {
1753-
local_txn.append(&mut $updates.0);
1726+
claim_requests = $updates.0;
17541727
watch_outputs.append(&mut $updates.1);
17551728
self.broadcasted_local_revokable_script = $updates.2;
17561729
}
@@ -1797,7 +1770,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
17971770
}
17981771
}
17991772

1800-
(local_txn, (commitment_txid, watch_outputs))
1773+
(claim_requests, (commitment_txid, watch_outputs))
18011774
}
18021775

18031776
/// Used by ChannelManager deserialization to broadcast the latest local state if its copy of
@@ -1867,14 +1840,11 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
18671840
watch_outputs.push(new_outputs);
18681841
}
18691842
if new_outpoints.is_empty() {
1870-
let (local_txn, new_outputs) = self.check_spend_local_transaction(&tx, height);
1871-
for tx in local_txn.iter() {
1872-
log_trace!(self, "Broadcast onchain {}", log_tx!(tx));
1873-
broadcaster.broadcast_transaction(tx);
1874-
}
1843+
let (mut new_outpoints, new_outputs) = self.check_spend_local_transaction(&tx, height);
18751844
if !new_outputs.1.is_empty() {
18761845
watch_outputs.push(new_outputs);
18771846
}
1847+
claimable_outpoints.append(&mut new_outpoints);
18781848
}
18791849
claimable_outpoints.append(&mut new_outpoints);
18801850
}
@@ -1904,14 +1874,11 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
19041874
if let Some(ref cur_local_tx) = self.current_local_signed_commitment_tx {
19051875
if should_broadcast {
19061876
if let Some(commitment_tx) = self.onchain_tx_handler.get_fully_signed_local_tx(self.channel_value_satoshis.unwrap()) {
1907-
let (txs, new_outputs, _) = self.broadcast_by_local_state(&commitment_tx, cur_local_tx);
1877+
let (mut new_outpoints, new_outputs, _) = self.broadcast_by_local_state(&commitment_tx, cur_local_tx);
19081878
if !new_outputs.is_empty() {
19091879
watch_outputs.push((cur_local_tx.txid.clone(), new_outputs));
19101880
}
1911-
for tx in txs {
1912-
log_trace!(self, "Broadcast onchain {}", log_tx!(tx));
1913-
broadcaster.broadcast_transaction(&tx);
1914-
}
1881+
claimable_outpoints.append(&mut new_outpoints);
19151882
}
19161883
}
19171884
}

lightning/src/ln/functional_tests.rs

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2353,16 +2353,16 @@ fn claim_htlc_outputs_single_tx() {
23532353
// ChannelMonitor: local commitment + local HTLC-timeout (2)
23542354

23552355
// Check the pair local commitment and HTLC-timeout broadcast due to HTLC expiration
2356+
assert_eq!(node_txn[2].input.len(), 1);
2357+
check_spends!(node_txn[2], chan_1.3.clone());
23562358
assert_eq!(node_txn[3].input.len(), 1);
2357-
check_spends!(node_txn[3], chan_1.3.clone());
2358-
assert_eq!(node_txn[0].input.len(), 1);
2359-
let witness_script = node_txn[0].input[0].witness.last().unwrap();
2359+
let witness_script = node_txn[3].input[0].witness.last().unwrap();
23602360
assert_eq!(witness_script.len(), OFFERED_HTLC_SCRIPT_WEIGHT); //Spending an offered htlc output
2361-
check_spends!(node_txn[0], node_txn[3].clone());
2361+
check_spends!(node_txn[3], node_txn[2].clone());
23622362

23632363
// Justice transactions are indices 1-2-4
2364+
assert_eq!(node_txn[0].input.len(), 1);
23642365
assert_eq!(node_txn[1].input.len(), 1);
2365-
assert_eq!(node_txn[2].input.len(), 1);
23662366
assert_eq!(node_txn[4].input.len(), 1);
23672367

23682368
fn get_txout(out_point: &BitcoinOutPoint, tx: &Transaction) -> Option<TxOut> {
@@ -2372,13 +2372,13 @@ fn claim_htlc_outputs_single_tx() {
23722372
None
23732373
}
23742374
}
2375+
node_txn[0].verify(|out|get_txout(out, &revoked_local_txn[0])).unwrap();
23752376
node_txn[1].verify(|out|get_txout(out, &revoked_local_txn[0])).unwrap();
2376-
node_txn[2].verify(|out|get_txout(out, &revoked_local_txn[0])).unwrap();
23772377
node_txn[4].verify(|out|get_txout(out, &revoked_local_txn[0])).unwrap();
23782378

23792379
let mut witness_lens = BTreeSet::new();
2380+
witness_lens.insert(node_txn[0].input[0].witness.last().unwrap().len());
23802381
witness_lens.insert(node_txn[1].input[0].witness.last().unwrap().len());
2381-
witness_lens.insert(node_txn[2].input[0].witness.last().unwrap().len());
23822382
witness_lens.insert(node_txn[4].input[0].witness.last().unwrap().len());
23832383
assert_eq!(witness_lens.len(), 3);
23842384
assert_eq!(*witness_lens.iter().skip(0).next().unwrap(), 77); // revoked to_local
@@ -2630,19 +2630,18 @@ fn test_htlc_on_chain_timeout() {
26302630
{
26312631
let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
26322632
assert_eq!(node_txn.len(), 5); // ChannelManager : 2 (commitment tx, HTLC-Timeout tx), ChannelMonitor : (local commitment tx + HTLC-timeout), timeout tx
2633+
assert_eq!(node_txn[1], node_txn[3]);
2634+
assert_eq!(node_txn[2], node_txn[4]);
26332635

2634-
assert_eq!(node_txn[2], node_txn[3]);
2635-
assert_eq!(node_txn[0], node_txn[4]);
2636-
2637-
check_spends!(node_txn[1], commitment_tx[0]);
2638-
assert_eq!(node_txn[1].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
2636+
check_spends!(node_txn[0], commitment_tx[0]);
2637+
assert_eq!(node_txn[0].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
26392638

2640-
check_spends!(node_txn[2], chan_2.3);
2641-
check_spends!(node_txn[0], node_txn[2]);
2642-
assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), 71);
2643-
assert_eq!(node_txn[0].clone().input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
2639+
check_spends!(node_txn[1], chan_2.3);
2640+
check_spends!(node_txn[2], node_txn[1]);
2641+
assert_eq!(node_txn[1].clone().input[0].witness.last().unwrap().len(), 71);
2642+
assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
26442643

2645-
timeout_tx = node_txn[1].clone();
2644+
timeout_tx = node_txn[0].clone();
26462645
node_txn.clear();
26472646
}
26482647

@@ -4300,8 +4299,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_timeout_tx() {
43004299
check_added_monitors!(nodes[0], 1);
43014300

43024301
let revoked_htlc_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
4303-
assert_eq!(revoked_htlc_txn.len(), 3);
4304-
assert_eq!(revoked_htlc_txn[0], revoked_htlc_txn[2]);
4302+
assert_eq!(revoked_htlc_txn.len(), 2);
43054303
assert_eq!(revoked_htlc_txn[0].input.len(), 1);
43064304
assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
43074305
check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]);
@@ -4357,8 +4355,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() {
43574355
check_added_monitors!(nodes[1], 1);
43584356
let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
43594357

4360-
assert_eq!(revoked_htlc_txn.len(), 3);
4361-
assert_eq!(revoked_htlc_txn[0], revoked_htlc_txn[2]);
4358+
assert_eq!(revoked_htlc_txn.len(), 2);
43624359
assert_eq!(revoked_htlc_txn[0].input.len(), 1);
43634360
assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
43644361
check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]);
@@ -7087,7 +7084,7 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
70877084
check_added_monitors!(nodes[1], 1);
70887085

70897086
let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
7090-
assert_eq!(revoked_htlc_txn.len(), 6);
7087+
assert_eq!(revoked_htlc_txn.len(), 4);
70917088
if revoked_htlc_txn[0].input[0].witness.last().unwrap().len() == ACCEPTED_HTLC_SCRIPT_WEIGHT {
70927089
assert_eq!(revoked_htlc_txn[0].input.len(), 1);
70937090
check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]);
@@ -7345,11 +7342,11 @@ fn test_set_outpoints_partial_claiming() {
73457342
let partial_claim_tx = {
73467343
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
73477344
assert_eq!(node_txn.len(), 3);
7348-
check_spends!(node_txn[0], node_txn[2].clone());
7349-
check_spends!(node_txn[1], node_txn[2].clone());
7350-
assert_eq!(node_txn[0].input.len(), 1);
7345+
check_spends!(node_txn[1], node_txn[0].clone());
7346+
check_spends!(node_txn[2], node_txn[0].clone());
73517347
assert_eq!(node_txn[1].input.len(), 1);
7352-
node_txn[0].clone()
7348+
assert_eq!(node_txn[2].input.len(), 1);
7349+
node_txn[1].clone()
73537350
};
73547351

73557352
// Broadcast partial claim on node A, should regenerate a claiming tx with HTLC dropped

lightning/src/ln/onchaintx.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -530,13 +530,14 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
530530
inputs_witnesses_weight += Self::get_witnesses_weight(if preimage.is_some() { &[InputDescriptors::OfferedHTLC] } else { &[InputDescriptors::ReceivedHTLC] });
531531
amt += *amount;
532532
},
533-
&InputMaterial::LocalHTLC { .. } => { return None; }
533+
&InputMaterial::LocalHTLC { .. } => {
534+
dynamic_fee = false;
535+
},
534536
&InputMaterial::Funding { .. } => {
535537
dynamic_fee = false;
536538
}
537539
}
538540
}
539-
540541
if dynamic_fee {
541542
let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight;
542543
let mut new_feerate;
@@ -598,11 +599,28 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
598599
} else {
599600
for (_, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() {
600601
match per_outp_material {
601-
&InputMaterial::LocalHTLC { .. } => {
602-
//TODO : Given that Local Commitment Transaction and HTLC-Timeout/HTLC-Success are counter-signed by peer, we can't
603-
// RBF them. Need a Lightning specs change and package relay modification :
604-
// https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
605-
return None;
602+
&InputMaterial::LocalHTLC { ref preimage, ref amount } => {
603+
let this_htlc_cache = if let Some(ref local_commitment) = self.local_commitment {
604+
if outp.txid == local_commitment.txid() {
605+
&self.current_htlc_cache
606+
} else if let Some(ref prev_commitment) = self.prev_local_commitment {
607+
if outp.txid == prev_commitment.txid() { &self.prev_htlc_cache } else { &None }
608+
} else { &None }
609+
} else { &None };
610+
if let &Some(ref htlc_cache) = this_htlc_cache {
611+
if let Some(htlc) = htlc_cache.per_htlc.get(&outp.vout) {
612+
if !htlc.0.offered && preimage.is_none() { return None; }; // If we don't have preimage for HTLC-Success, don't try to generate
613+
let htlc_secret = if !htlc.0.offered { preimage } else { &None }; // If we have a preimage for a HTLC-Timeout, don't use it that's likely a duplicate HTLC hash
614+
615+
let mut htlc_tx = chan_utils::build_htlc_transaction(&outp.txid, htlc_cache.feerate_per_kw, self.local_csv, &htlc.0, &htlc_cache.local_keys.a_delayed_payment_key, &htlc_cache.local_keys.revocation_key);
616+
self.key_storage.sign_htlc_transaction(&mut htlc_tx, htlc.1.as_ref().unwrap(), htlc_secret, &htlc.0, &htlc_cache.local_keys.a_htlc_key, &htlc_cache.local_keys.b_htlc_key, &htlc_cache.local_keys.revocation_key, &htlc_cache.local_keys.per_commitment_point, &self.secp_ctx);
617+
618+
let feerate = (amount - htlc_tx.output[0].value) * 1000 / htlc_tx.get_weight() as u64;
619+
// Timer set to $NEVER given we can't bump tx without anchor outputs
620+
log_trace!(self, "Going to broadcast Local HTLC-{} claiming HTLC output {} from {}...", if preimage.is_some() { "Success" } else { "Timeout" }, outp.vout, outp.txid);
621+
return Some((None, feerate, htlc_tx));
622+
}
623+
}
606624
},
607625
&InputMaterial::Funding { ref channel_value } => {
608626
if self.local_commitment.is_some() {

0 commit comments

Comments
 (0)