Skip to content

Commit 1233ac3

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 1233ac3

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

lightning/src/chain/channelmonitor.rs

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

3302+
pub fn cancel_prev_commitment_claims<L: Deref>(
3303+
&mut self, logger: &L, confirmed_commitment_txid: &Txid
3304+
) where L::Target: Logger {
3305+
for (counterparty_commitment_txid, _) in &self.counterparty_commitment_txn_on_chain {
3306+
if counterparty_commitment_txid == confirmed_commitment_txid {
3307+
continue;
3308+
}
3309+
for (htlc, _) in self.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) {
3310+
log_trace!(logger, "Canceling claims for previously confirmed counterparty commitment {}",
3311+
counterparty_commitment_txid);
3312+
let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 };
3313+
if let Some(vout) = htlc.transaction_output_index {
3314+
outpoint.vout = vout;
3315+
self.onchain_tx_handler.abandon_claim(&outpoint);
3316+
}
3317+
}
3318+
}
3319+
if self.holder_tx_signed {
3320+
if self.current_holder_commitment_tx.txid != *confirmed_commitment_txid {
3321+
log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}",
3322+
self.current_holder_commitment_tx.txid);
3323+
let mut outpoint = BitcoinOutPoint { txid: self.current_holder_commitment_tx.txid, vout: 0 };
3324+
for (htlc, _, _) in &self.current_holder_commitment_tx.htlc_outputs {
3325+
if let Some(vout) = htlc.transaction_output_index {
3326+
outpoint.vout = vout;
3327+
self.onchain_tx_handler.abandon_claim(&outpoint);
3328+
}
3329+
}
3330+
}
3331+
if let Some(prev_holder_commitment_tx) = &self.prev_holder_signed_commitment_tx {
3332+
if prev_holder_commitment_tx.txid != *confirmed_commitment_txid {
3333+
log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}",
3334+
prev_holder_commitment_tx.txid);
3335+
let mut outpoint = BitcoinOutPoint { txid: prev_holder_commitment_tx.txid, vout: 0 };
3336+
for (htlc, _, _) in &prev_holder_commitment_tx.htlc_outputs {
3337+
if let Some(vout) = htlc.transaction_output_index {
3338+
outpoint.vout = vout;
3339+
self.onchain_tx_handler.abandon_claim(&outpoint);
3340+
}
3341+
}
3342+
}
3343+
}
3344+
} else {
3345+
// No previous claim.
3346+
}
3347+
}
3348+
33023349
pub fn get_latest_holder_commitment_txn<L: Deref>(&mut self, logger: &L) -> Vec<Transaction> where L::Target: Logger {
33033350
log_debug!(logger, "Getting signed latest holder commitment transaction!");
33043351
self.holder_tx_signed = true;
@@ -3504,6 +3551,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35043551
commitment_tx_to_counterparty_output,
35053552
},
35063553
});
3554+
self.cancel_prev_commitment_claims(&logger, &txid);
35073555
}
35083556
}
35093557
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)