Avoid duplicate on-chain HTLC claims after replay#4583
Avoid duplicate on-chain HTLC claims after replay#4583joostjager wants to merge 9 commits intolightningdevkit:mainfrom
Conversation
|
👋 I see @valentinewallace was un-assigned. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4583 +/- ##
==========================================
+ Coverage 87.16% 87.18% +0.02%
==========================================
Files 161 161
Lines 109251 109520 +269
Branches 109251 109520 +269
==========================================
+ Hits 95230 95487 +257
- Misses 11547 11558 +11
- Partials 2474 2475 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
531576d to
9e0f886
Compare
|
I've completed a thorough review of every hunk in this PR, tracing through the following key areas:
No issues found. |
| requests.retain(|req| { | ||
| let outpoint = req.outpoint(); | ||
| if self.claimable_outpoints.get(outpoint).is_some() { | ||
| if self.is_outpoint_spend_waiting_threshold_conf(outpoint) { |
There was a problem hiding this comment.
Does this break for reorgs where a counterparty's transaction gets unconfirmed? ie if we have a event awaiting confirmations for the counterparty claiming an outpoint, but then that gets reorg'd out and not replayed, do we still manage to broadcast our own claim (and RBF it)?
I assume that this case is only reachable if we receive a preimage after a counterparty's timeout claim is confirming?
There was a problem hiding this comment.
I think on a reorg the item in onchain_events_awaiting_threshold_conf is revived again? Basically the claim is passed around to different data structures, but never lost?
|
|
||
| fn is_htlc_output_spent_on_chain(&self, htlc: &HTLCOutputInCommitment) -> bool { | ||
| if let Some(transaction_output_index) = htlc.transaction_output_index { | ||
| // This is a monitor-level HTLC generation filter. OnchainTxHandler |
There was a problem hiding this comment.
So why exactly do we need the duplicate?
There was a problem hiding this comment.
It stems from OnchainTxHandler not persisting state
There was a problem hiding this comment.
The duplication covers a restart/replay gap after finality.
OnchainTxHandler persists active claim state, but drops it once the claim reaches anti-reorg finality. It does not keep an ever-growing resolved-outpoint tombstone set.
After that cleanup, a replayed preimage update can look like a fresh claim request to OnchainTxHandler. ChannelMonitor does persist final HTLC resolution in htlcs_resolved_on_chain, so this filter uses that state to suppress only already-resolved HTLC outpoints.
| // still guards package state for outpoints split out by confirmed | ||
| // spends; here we avoid recreating HTLC claim requests once the | ||
| // monitor has observed resolution. | ||
| self.onchain_events_awaiting_threshold_conf.iter().any(|entry| match entry.event { |
There was a problem hiding this comment.
Same issue as the previous commit - does this break broadcasting our conflicting claim on reorg?
There was a problem hiding this comment.
Hm, here indeed a claim is never submitted and also cannot be resurrected.
There was a problem hiding this comment.
Now only checking for deeply confirmed outpoints, but need to look at the consequences of that.
There was a problem hiding this comment.
Yea, I would imagine any issues we have with deeply-confirmed would apply to less-confirmed as well, only more likely.
There was a problem hiding this comment.
I've added resolve HTLC spends at anti-reorg finality. Previously, there was a gap between anti-reorg finality and CSV maturity where the monitor could resubmit a claim to the onchain handler. The commit closes that by adding HTLC spends to htlcs_resolved_on_chain at anti-reorg finality.
That should not affect balance reporting, since any CSV-delayed output remains tracked separately. Before anti-reorg finality, the claim still goes to the onchain handler, so its reorg handling stays intact. After anti-reorg finality, replayed claims are suppressed by the monitor.
Let me know what you think.
37d6b77 to
1c3b725
Compare
Have ChannelMonitor hand singular ClaimRequests to OnchainTxHandler. Convert them to PackageTemplates only after duplicate filtering. This makes the single-outpoint invariant explicit at that boundary.
Clarify ChannelMonitor comments around on-chain event thresholds. Some events only wait for anti-reorg finality, while CSV-delayed outputs wait until spendable through the same threshold queue.
Move repeated OnchainTxHandler setup into shared test helpers so the claim-replay coverage can focus on the behavior under test.
Add a monitor test for an inbound HTLC claimed by preimage from a holder commitment. Confirm that the claimable balance remains unchanged after the HTLC-success spend reaches anti-reorg finality but before the CSV-delayed output is spendable.
Treat HTLCSpendConfirmation entries as irrevocably resolved once the commitment HTLC output spend reaches anti-reorg finality. Do not wait for CSV maturity of any delayed output created by that spend. Delayed outputs remain tracked separately as MaturingOutput entries, keeping claimable balances alive until they are CSV-mature and can be surfaced as SpendableOutputs.
1c3b725 to
a3fbe2d
Compare
A replayed holder HTLC claim may arrive as a single-outpoint request after earlier requests were merged into a delayed package. Check whether an existing delayed package already covers the new request instead of requiring exact outpoint-set equality. Add focused OnchainTxHandler coverage and a ChannelMonitor regression through claim_funds for both current anchor variants.
When a transaction spends one outpoint from a delayed package, the split outpoint is tracked as a ContentiousOutpoint until the spend reaches anti-reorg finality. Reject replayed claim requests for those pending-spent outpoints so they are not added back before the spend reaches anti-reorg finality or reorgs out. Add an OnchainTxHandler regression that replays a holder claim during that pending-spent window and verifies reorg resurrection still works.
Filter regenerated HTLC claim requests once ChannelMonitor has persisted anti-reorg finality for the commitment HTLC output spend. This keeps replayed preimage updates from recreating claims after OnchainTxHandler has cleaned up its active retry state, relying on the monitor's persisted HTLC resolution state.
Hash HTLC claim outpoints in canonical order so the same logical HTLC set produces the same ClaimId regardless of descriptor order. Add a unit test covering reversed descriptor order.
a3fbe2d to
3196617
Compare
Fixes #4572