Skip to content

Commit 876cf15

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 415cbf0 commit 876cf15

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
@@ -3300,6 +3300,53 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
33003300
}
33013301
}
33023302

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

85408540
// Watchtower Alice should have broadcast a commitment/HTLC-timeout
8541-
let alice_state = {
8541+
{
85428542
let mut txn = alice_broadcaster.txn_broadcast();
85438543
assert_eq!(txn.len(), 2);
8544-
txn.remove(0)
8544+
check_spends!(txn[0], chan_1.3);
8545+
check_spends!(txn[1], txn[0]);
85458546
};
85468547

85478548
// Copy ChainMonitor to simulate watchtower Bob and make it receive a commitment update first.
@@ -8610,11 +8611,8 @@ fn test_concurrent_monitor_claim() {
86108611
check_added_monitors(&nodes[0], 1);
86118612
{
86128613
let htlc_txn = alice_broadcaster.txn_broadcast();
8613-
assert_eq!(htlc_txn.len(), 2);
8614+
assert_eq!(htlc_txn.len(), 1);
86148615
check_spends!(htlc_txn[0], bob_state_y);
8615-
// Alice doesn't clean up the old HTLC claim since it hasn't seen a conflicting spend for
8616-
// it. However, she should, because it now has an invalid parent.
8617-
check_spends!(htlc_txn[1], alice_state);
86188616
}
86198617
}
86208618

lightning/src/ln/reorg_tests.rs

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

665665
mine_transaction(&nodes[0], &commitment_tx_b);
666666
mine_transaction(&nodes[1], &commitment_tx_b);
667+
if nodes[1].connect_style.borrow().updates_best_block_first() {
668+
let _ = nodes[1].tx_broadcaster.txn_broadcast();
669+
}
667670

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

0 commit comments

Comments
 (0)