Skip to content

Commit 7dcee4f

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 0c67753 commit 7dcee4f

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
@@ -3356,6 +3356,58 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
33563356
}
33573357
}
33583358

3359+
/// Cancels any existing pending claims for a commitment that previously confirmed and has now
3360+
/// been replaced by another.
3361+
pub fn cancel_prev_commitment_claims<L: Deref>(
3362+
&mut self, logger: &L, confirmed_commitment_txid: &Txid
3363+
) where L::Target: Logger {
3364+
for (counterparty_commitment_txid, _) in &self.counterparty_commitment_txn_on_chain {
3365+
// Cancel any pending claims for counterparty commitments we've seen confirm.
3366+
if counterparty_commitment_txid == confirmed_commitment_txid {
3367+
continue;
3368+
}
3369+
for (htlc, _) in self.counterparty_claimable_outpoints.get(counterparty_commitment_txid).unwrap_or(&vec![]) {
3370+
log_trace!(logger, "Canceling claims for previously confirmed counterparty commitment {}",
3371+
counterparty_commitment_txid);
3372+
let mut outpoint = BitcoinOutPoint { txid: *counterparty_commitment_txid, vout: 0 };
3373+
if let Some(vout) = htlc.transaction_output_index {
3374+
outpoint.vout = vout;
3375+
self.onchain_tx_handler.abandon_claim(&outpoint);
3376+
}
3377+
}
3378+
}
3379+
if self.holder_tx_signed {
3380+
// If we've signed, we may have broadcast either commitment (prev or current), and
3381+
// attempted to claim from it immediately without waiting for a confirmation.
3382+
if self.current_holder_commitment_tx.txid != *confirmed_commitment_txid {
3383+
log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}",
3384+
self.current_holder_commitment_tx.txid);
3385+
let mut outpoint = BitcoinOutPoint { txid: self.current_holder_commitment_tx.txid, vout: 0 };
3386+
for (htlc, _, _) in &self.current_holder_commitment_tx.htlc_outputs {
3387+
if let Some(vout) = htlc.transaction_output_index {
3388+
outpoint.vout = vout;
3389+
self.onchain_tx_handler.abandon_claim(&outpoint);
3390+
}
3391+
}
3392+
}
3393+
if let Some(prev_holder_commitment_tx) = &self.prev_holder_signed_commitment_tx {
3394+
if prev_holder_commitment_tx.txid != *confirmed_commitment_txid {
3395+
log_trace!(logger, "Canceling claims for previously broadcast holder commitment {}",
3396+
prev_holder_commitment_tx.txid);
3397+
let mut outpoint = BitcoinOutPoint { txid: prev_holder_commitment_tx.txid, vout: 0 };
3398+
for (htlc, _, _) in &prev_holder_commitment_tx.htlc_outputs {
3399+
if let Some(vout) = htlc.transaction_output_index {
3400+
outpoint.vout = vout;
3401+
self.onchain_tx_handler.abandon_claim(&outpoint);
3402+
}
3403+
}
3404+
}
3405+
}
3406+
} else {
3407+
// No previous claim.
3408+
}
3409+
}
3410+
33593411
fn get_latest_holder_commitment_txn<L: Deref>(
33603412
&mut self, logger: &WithChannelMonitor<L>,
33613413
) -> Vec<Transaction> where L::Target: Logger {
@@ -3568,6 +3620,10 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35683620
commitment_tx_to_counterparty_output,
35693621
},
35703622
});
3623+
// Now that we've detected a confirmed commitment transaction, attempt to cancel
3624+
// pending claims for any commitments that were previously confirmed such that
3625+
// we don't continue claiming inputs that no longer exist.
3626+
self.cancel_prev_commitment_claims(&logger, &txid);
35713627
}
35723628
}
35733629
if tx.input.len() >= 1 {

lightning/src/chain/onchaintx.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,25 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
676676
None
677677
}
678678

679+
pub fn abandon_claim(&mut self, outpoint: &BitcoinOutPoint) {
680+
let claim_id = self.claimable_outpoints.get(outpoint).map(|(claim_id, _)| *claim_id)
681+
.or_else(|| {
682+
self.pending_claim_requests.iter()
683+
.find(|(_, claim)| claim.outpoints().iter().any(|claim_outpoint| *claim_outpoint == outpoint))
684+
.map(|(claim_id, _)| *claim_id)
685+
});
686+
if let Some(claim_id) = claim_id {
687+
if let Some(claim) = self.pending_claim_requests.remove(&claim_id) {
688+
for outpoint in claim.outpoints() {
689+
self.claimable_outpoints.remove(&outpoint);
690+
}
691+
}
692+
} else {
693+
self.locktimed_packages.values_mut().for_each(|claims|
694+
claims.retain(|claim| !claim.outpoints().iter().any(|claim_outpoint| *claim_outpoint == outpoint)));
695+
}
696+
}
697+
679698
/// Upon channelmonitor.block_connected(..) or upon provision of a preimage on the forward link
680699
/// for this channel, provide new relevant on-chain transactions and/or new claim requests.
681700
/// 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
@@ -8568,10 +8568,11 @@ fn test_concurrent_monitor_claim() {
85688568
watchtower_alice.chain_monitor.block_connected(&block, HTLC_TIMEOUT_BROADCAST);
85698569

85708570
// Watchtower Alice should have broadcast a commitment/HTLC-timeout
8571-
let alice_state = {
8571+
{
85728572
let mut txn = alice_broadcaster.txn_broadcast();
85738573
assert_eq!(txn.len(), 2);
8574-
txn.remove(0)
8574+
check_spends!(txn[0], chan_1.3);
8575+
check_spends!(txn[1], txn[0]);
85758576
};
85768577

85778578
// Copy ChainMonitor to simulate watchtower Bob and make it receive a commitment update first.
@@ -8640,11 +8641,8 @@ fn test_concurrent_monitor_claim() {
86408641
check_added_monitors(&nodes[0], 1);
86418642
{
86428643
let htlc_txn = alice_broadcaster.txn_broadcast();
8643-
assert_eq!(htlc_txn.len(), 2);
8644+
assert_eq!(htlc_txn.len(), 1);
86448645
check_spends!(htlc_txn[0], bob_state_y);
8645-
// Alice doesn't clean up the old HTLC claim since it hasn't seen a conflicting spend for
8646-
// it. However, she should, because it now has an invalid parent.
8647-
check_spends!(htlc_txn[1], alice_state);
86488646
}
86498647
}
86508648

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)