Skip to content

Commit 83a9e4b

Browse files
author
Antoine Riard
committed
Watch outputs of revoked HTLC-transactions
Bumping of justice txn on revoked HTLC-Success/HTLC-timeout is triggered until our claim is confirmed onchain with at least ANTI_REORG_DELAY_SAFE. Before this patch, we weren't tracking them in check_spend_remote_htlc, leading us to infinite bumps. Fix #411
1 parent b99d819 commit 83a9e4b

File tree

3 files changed

+23
-15
lines changed

3 files changed

+23
-15
lines changed

lightning/src/ln/channelmonitor.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,22 +1668,22 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16681668
}
16691669

16701670
/// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key
1671-
fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> Vec<ClaimRequest> {
1672-
//TODO: send back new outputs to guarantee pending_claim_request consistency
1671+
fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> (Vec<ClaimRequest>, (Sha256dHash, Vec<TxOut>)) {
1672+
let htlc_txid = tx.txid();
16731673
if tx.input.len() != 1 || tx.output.len() != 1 || tx.input[0].witness.len() != 5 {
1674-
return Vec::new()
1674+
return (Vec::new(), (htlc_txid, Vec::new()))
16751675
}
16761676

16771677
macro_rules! ignore_error {
16781678
( $thing : expr ) => {
16791679
match $thing {
16801680
Ok(a) => a,
1681-
Err(_) => return Vec::new()
1681+
Err(_) => return (Vec::new(), (htlc_txid, Vec::new()))
16821682
}
16831683
};
16841684
}
16851685

1686-
let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return Vec::new(); };
1686+
let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return (Vec::new(), (htlc_txid, Vec::new())); };
16871687
let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret));
16881688
let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
16891689
let (revocation_pubkey, revocation_key) = match self.key_storage {
@@ -1694,16 +1694,15 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16941694
Storage::Watchtower { .. } => { unimplemented!() }
16951695
};
16961696
let delayed_key = match self.their_delayed_payment_base_key {
1697-
None => return Vec::new(),
1697+
None => return (Vec::new(), (htlc_txid, Vec::new())),
16981698
Some(their_delayed_payment_base_key) => ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &per_commitment_point, &their_delayed_payment_base_key)),
16991699
};
17001700
let redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.our_to_self_delay, &delayed_key);
1701-
let htlc_txid = tx.txid(); //TODO: This is gonna be a performance bottleneck for watchtowers!
17021701

17031702
log_trace!(self, "Remote HTLC broadcast {}:{}", htlc_txid, 0);
17041703
let witness_data = InputMaterial::Revoked { witness_script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: tx.output[0].value };
17051704
let claimable_outpoints = vec!(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: htlc_txid, vout: 0}, witness_data });
1706-
claimable_outpoints
1705+
(claimable_outpoints, (htlc_txid, tx.output.clone()))
17071706
}
17081707

17091708
fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx, delayed_payment_base_key: &SecretKey) -> (Vec<Transaction>, Vec<SpendableOutputDescriptor>, Vec<TxOut>) {
@@ -2019,8 +2018,9 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
20192018
}
20202019
} else {
20212020
if let Some(&(commitment_number, _)) = self.remote_commitment_txn_on_chain.get(&prevout.txid) {
2022-
let mut new_outpoints = self.check_spend_remote_htlc(&tx, commitment_number, height);
2021+
let (mut new_outpoints, new_outputs) = self.check_spend_remote_htlc(&tx, commitment_number, height);
20232022
claimable_outpoints.append(&mut new_outpoints);
2023+
watch_outputs.push(new_outputs);
20242024
}
20252025
}
20262026
}

lightning/src/ln/functional_test_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,9 +1097,9 @@ pub fn test_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, chan: &(msgs::Cha
10971097
/// HTLC transaction.
10981098
pub fn test_revoked_htlc_claim_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, revoked_tx: Transaction, commitment_revoked_tx: Transaction) {
10991099
let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap();
1100-
// We should issue a 2nd transaction if one htlc is dropped from initial claiming tx
1101-
// but sometimes not as feerate is too-low
1102-
if node_txn.len() != 1 && node_txn.len() != 2 { assert!(false); }
1100+
// We may issue multiple claiming transaction on revoked outputs due to block rescan
1101+
// for revoked htlc outputs
1102+
if node_txn.len() != 1 && node_txn.len() != 2 && node_txn.len() != 3 { assert!(false); }
11031103
node_txn.retain(|tx| {
11041104
if tx.input.len() == 1 && tx.input[0].previous_output.txid == revoked_tx.txid() {
11051105
check_spends!(tx, revoked_tx);

lightning/src/ln/functional_tests.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,8 +2127,10 @@ fn test_justice_tx() {
21272127
test_txn_broadcast(&nodes[1], &chan_5, None, HTLCType::NONE);
21282128

21292129
nodes[0].block_notifier.block_connected(&Block { header, txdata: vec![revoked_local_txn[0].clone()] }, 1);
2130+
// Verify broadcast of revoked HTLC-timeout
21302131
let node_txn = test_txn_broadcast(&nodes[0], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::TIMEOUT);
21312132
header = BlockHeader { version: 0x20000000, prev_blockhash: header.bitcoin_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
2133+
// Broadcast revoked HTLC-timeout on node 1
21322134
nodes[1].block_notifier.block_connected(&Block { header, txdata: vec![node_txn[1].clone()] }, 1);
21332135
test_revoked_htlc_claim_txn_broadcast(&nodes[1], node_txn[1].clone(), revoked_local_txn[0].clone());
21342136
}
@@ -4238,7 +4240,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() {
42384240

42394241
// Check A's ChannelMonitor was able to generate the right spendable output descriptor
42404242
let spend_txn = check_spendable_outputs!(nodes[0], 1);
4241-
assert_eq!(spend_txn.len(), 4);
4243+
assert_eq!(spend_txn.len(), 5); // Duplicated SpendableOutput due to block rescan after revoked htlc output tracking
42424244
assert_eq!(spend_txn[0], spend_txn[2]);
42434245
check_spends!(spend_txn[0], revoked_local_txn[0]); // spending to_remote output from revoked local tx
42444246
check_spends!(spend_txn[1], node_txn[0]); // spending justice tx output from revoked local tx htlc received output
@@ -6954,7 +6956,7 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
69546956

69556957
assert_eq!(node_txn[0].input.len(), 2);
69566958
check_spends!(node_txn[0], revoked_htlc_txn[0], revoked_htlc_txn[1]);
6957-
//// Verify bumped tx is different and 25% bump heuristic
6959+
// Verify bumped tx is different and 25% bump heuristic
69586960
assert_ne!(first, node_txn[0].txid());
69596961
let fee_2 = revoked_htlc_txn[0].output[0].value + revoked_htlc_txn[1].output[0].value - node_txn[0].output[0].value;
69606962
let feerate_2 = fee_2 * 1000 / node_txn[0].get_weight() as u64;
@@ -6969,7 +6971,13 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
69696971
connect_blocks(&nodes[0].block_notifier, 20, 145, true, header_145.bitcoin_hash());
69706972
{
69716973
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
6972-
assert_eq!(node_txn.len(), 1); //TODO: fix check_spend_remote_htlc lack of watch output
6974+
// We verify than no new transaction has been broadcast because previously
6975+
// we were buggy on this exact behavior by not tracking for monitoring remote HTLC outputs (see #411)
6976+
// which means we wouldn't see a spend of them by a justice tx and bumped justice tx
6977+
// were generated forever instead of safe cleaning after confirmation and ANTI_REORG_SAFE_DELAY blocks.
6978+
// Enforce spending of revoked htlc output by claiming transaction remove request as expected and dry
6979+
// up bumped justice generation.
6980+
assert_eq!(node_txn.len(), 0);
69736981
node_txn.clear();
69746982
}
69756983
check_closed_broadcast!(nodes[0], false);

0 commit comments

Comments
 (0)