Skip to content

Commit 1876e67

Browse files
committed
Cancel previous commitment claims on newly confirmed commitment
Once a commitment transaction is broadcast/confirms, we may need to claim some of the HTLCs in it. These claims are sent as requests to the `OnchainTxHandler`, which will bump their feerate as they remain unconfirmed. When said commitment transaction becomes unconfirmed though, and another commitment confirms instead, i.e., a reorg happens, the `OnchainTxHandler` doesn't have any insight into whether these claims are still valid or not, so it continues attempting to claim the HTLCs from the previous commitment (now unconfirmed) forever, along with the HTLCs from the newly confirmed commitment.
1 parent 74078c4 commit 1876e67

File tree

4 files changed

+82
-6
lines changed

4 files changed

+82
-6
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3299,6 +3299,58 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
32993299
}
33003300
}
33013301

3302+
/// Cancels any existing pending claims for a commitment that previously confirmed and has now
3303+
/// been replaced by another.
3304+
pub fn cancel_prev_commitment_claims<L: Deref>(
3305+
&mut self, logger: &L, confirmed_commitment_txid: &Txid
3306+
) where L::Target: Logger {
3307+
for (counterparty_commitment_txid, _) in &self.counterparty_commitment_txn_on_chain {
3308+
// Cancel any pending claims for counterparty commitments we've seen confirm.
3309+
if counterparty_commitment_txid == confirmed_commitment_txid {
3310+
continue;
3311+
}
3312+
for (htlc, _) in self.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) {
3313+
log_trace!(logger, "Canceling claims for previously confirmed counterparty commitment {}",
3314+
counterparty_commitment_txid);
3315+
let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 };
3316+
if let Some(vout) = htlc.transaction_output_index {
3317+
outpoint.vout = vout;
3318+
self.onchain_tx_handler.abandon_claim(&outpoint);
3319+
}
3320+
}
3321+
}
3322+
if self.holder_tx_signed {
3323+
// If we've signed, we may have broadcast either commitment (prev or current), and
3324+
// attempted to claim from it immediately without waiting for a confirmation.
3325+
if self.current_holder_commitment_tx.txid != *confirmed_commitment_txid {
3326+
log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}",
3327+
self.current_holder_commitment_tx.txid);
3328+
let mut outpoint = BitcoinOutPoint { txid: self.current_holder_commitment_tx.txid, vout: 0 };
3329+
for (htlc, _, _) in &self.current_holder_commitment_tx.htlc_outputs {
3330+
if let Some(vout) = htlc.transaction_output_index {
3331+
outpoint.vout = vout;
3332+
self.onchain_tx_handler.abandon_claim(&outpoint);
3333+
}
3334+
}
3335+
}
3336+
if let Some(prev_holder_commitment_tx) = &self.prev_holder_signed_commitment_tx {
3337+
if prev_holder_commitment_tx.txid != *confirmed_commitment_txid {
3338+
log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}",
3339+
prev_holder_commitment_tx.txid);
3340+
let mut outpoint = BitcoinOutPoint { txid: prev_holder_commitment_tx.txid, vout: 0 };
3341+
for (htlc, _, _) in &prev_holder_commitment_tx.htlc_outputs {
3342+
if let Some(vout) = htlc.transaction_output_index {
3343+
outpoint.vout = vout;
3344+
self.onchain_tx_handler.abandon_claim(&outpoint);
3345+
}
3346+
}
3347+
}
3348+
}
3349+
} else {
3350+
// No previous claim.
3351+
}
3352+
}
3353+
33023354
pub fn get_latest_holder_commitment_txn<L: Deref>(&mut self, logger: &L) -> Vec<Transaction> where L::Target: Logger {
33033355
log_debug!(logger, "Getting signed latest holder commitment transaction!");
33043356
self.holder_tx_signed = true;
@@ -3504,6 +3556,10 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35043556
commitment_tx_to_counterparty_output,
35053557
},
35063558
});
3559+
// Now that we've detected a confirmed commitment transaction, attempt to cancel
3560+
// pending claims for any commitments that were previously confirmed such that
3561+
// we don't continue claiming inputs that no longer exist.
3562+
self.cancel_prev_commitment_claims(&logger, &txid);
35073563
}
35083564
}
35093565
if tx.input.len() >= 1 {

lightning/src/chain/onchaintx.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,25 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
679679
None
680680
}
681681

682+
pub fn abandon_claim(&mut self, outpoint: &BitcoinOutPoint) {
683+
let claim_id = self.claimable_outpoints.get(outpoint).map(|(claim_id, _)| *claim_id)
684+
.or_else(|| {
685+
self.pending_claim_requests.iter()
686+
.find(|(_, claim)| claim.outpoints().iter().any(|claim_outpoint| *claim_outpoint == outpoint))
687+
.map(|(claim_id, _)| *claim_id)
688+
});
689+
if let Some(claim_id) = claim_id {
690+
if let Some(claim) = self.pending_claim_requests.remove(&claim_id) {
691+
for outpoint in claim.outpoints() {
692+
self.claimable_outpoints.remove(&outpoint);
693+
}
694+
}
695+
} else {
696+
self.locktimed_packages.values_mut().for_each(|claims|
697+
claims.retain(|claim| !claim.outpoints().iter().any(|claim_outpoint| *claim_outpoint == outpoint)));
698+
}
699+
}
700+
682701
/// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link
683702
/// for this channel, provide new relevant on-chain transactions and/or new claim requests.
684703
/// Together with `update_claims_view_from_matched_txn` this used to be named

lightning/src/ln/functional_tests.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8563,10 +8563,11 @@ fn test_concurrent_monitor_claim() {
85638563
watchtower_alice.chain_monitor.block_connected(&block, HTLC_TIMEOUT_BROADCAST);
85648564

85658565
// Watchtower Alice should have broadcast a commitment/HTLC-timeout
8566-
let alice_state = {
8566+
{
85678567
let mut txn = alice_broadcaster.txn_broadcast();
85688568
assert_eq!(txn.len(), 2);
8569-
txn.remove(0)
8569+
check_spends!(txn[0], chan_1.3);
8570+
check_spends!(txn[1], txn[0]);
85708571
};
85718572

85728573
// Copy ChainMonitor to simulate watchtower Bob and make it receive a commitment update first.
@@ -8635,11 +8636,8 @@ fn test_concurrent_monitor_claim() {
86358636
check_added_monitors(&nodes[0], 1);
86368637
{
86378638
let htlc_txn = alice_broadcaster.txn_broadcast();
8638-
assert_eq!(htlc_txn.len(), 2);
8639+
assert_eq!(htlc_txn.len(), 1);
86398640
check_spends!(htlc_txn[0], bob_state_y);
8640-
// Alice doesn't clean up the old HTLC claim since it hasn't seen a conflicting spend for
8641-
// it. However, she should, because it now has an invalid parent.
8642-
check_spends!(htlc_txn[1], alice_state);
86438641
}
86448642
}
86458643

lightning/src/ln/reorg_tests.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,9 @@ fn test_htlc_preimage_claim_holder_commitment_after_counterparty_commitment_reor
666666

667667
mine_transaction(&nodes[0], &commitment_tx_b);
668668
mine_transaction(&nodes[1], &commitment_tx_b);
669+
if nodes[1].connect_style.borrow().updates_best_block_first() {
670+
let _ = nodes[1].tx_broadcaster.txn_broadcast();
671+
}
669672

670673
// Provide the preimage now, such that we only claim from the holder commitment (since it's
671674
// currently confirmed) and not the counterparty's.

0 commit comments

Comments
 (0)