Skip to content

Commit

Permalink
contractcourt: use the sweeper for HTLC offered remote timeout resolu…
Browse files Browse the repository at this point in the history
…tion

In this commit, we bring the timeout resolver more in line with the
success resolver by using the sweeper to handle the HTLC offered remote
timeout outputs. These are outputs that we can sweep directly from the
remote party's commitment transaction when they broadcast their version
of the commitment transaction.

With this change, we slim down the scope slightly by only doing this for
anchor channels. Non-anchor channels will continue to use the
utxonursery for this output type for now.
  • Loading branch information
Roasbeef committed Sep 4, 2024
1 parent ce27e4e commit fb5c245
Showing 1 changed file with 70 additions and 0 deletions.
70 changes: 70 additions & 0 deletions contractcourt/htlc_timeout_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,59 @@ func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error {
return h.Checkpoint(h)
}

// sweepDirectHtlcOutput sends the direct spend of the HTLC output to the
// sweeper. This is used when the remote party goes on chain, and we're able to
// sweep an HTLC we offered after a timeout. Only the CLTV encumbered outputs
// are resolved via this path.
func (h *htlcTimeoutResolver) sweepDirectHtlcOutput(immediate bool) error {
var htlcWitnessType input.StandardWitnessType
if h.isTaproot() {
htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout
} else {
htlcWitnessType = input.HtlcOfferedRemoteTimeout
}

sweepInput := input.NewCsvInputWithCltv(
&h.htlcResolution.ClaimOutpoint, htlcWitnessType,
&h.htlcResolution.SweepSignDesc, h.broadcastHeight,
h.htlcResolution.CsvDelay, h.htlcResolution.Expiry,
)

// Calculate the budget.
//
// TODO(yy): the budget is twice the output's value, which is needed as
// we don't force sweep the output now. To prevent cascading force
// closes, we use all its output value plus a wallet input as the
// budget. This is a temporary solution until we can optionally cancel
// the incoming HTLC, more details in,
// - https://github.com/lightningnetwork/lnd/issues/7969
budget := calculateBudget(
btcutil.Amount(sweepInput.SignDesc().Output.Value), 2, 0,
)

log.Infof("%T(%x): offering offered remote timeout HTLC output to "+
"sweeper with deadline %v and budget=%v at height=%v",
h, h.htlc.RHash[:], h.incomingHTLCExpiryHeight, budget,
h.broadcastHeight)

_, err := h.Sweeper.SweepInput(
sweepInput,
sweep.Params{
Budget: budget,

// This is an outgoing HTLC, so we want to make sure
// that we sweep it before the incoming HTLC expires.
DeadlineHeight: h.incomingHTLCExpiryHeight,
Immediate: immediate,
},
)
if err != nil {
return err
}

return nil
}

// spendHtlcOutput handles the initial spend of an HTLC output via the timeout
// clause. If this is our local commitment, the second-level timeout TX will be
// used to spend the output into the next stage. If this is the remote
Expand All @@ -582,6 +635,16 @@ func (h *htlcTimeoutResolver) spendHtlcOutput(
return nil, err
}

// If this is an anchor, and there's no second level txn (direct spend
// from remote commitment), then we'll just send this directly to the
// sweeper.
case h.isTaproot() && h.htlcResolution.SignedTimeoutTx == nil:
if err := h.sweepDirectHtlcOutput(immediate); err != nil {
log.Errorf("Sending direct spend to sweeper: %v", err)

return nil, err
}

// If we have no SignDetails, and we haven't already sent the output to
// the utxo nursery, then we'll do so now.
case h.htlcResolution.SignDetails == nil && !h.outputIncubating:
Expand Down Expand Up @@ -763,6 +826,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
h.htlcResolution.CsvDelay,
uint32(commitSpend.SpendingHeight), h.htlc.RHash,
)

// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
Expand Down Expand Up @@ -794,6 +858,12 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
claimOutpoint = *op
fallthrough

// If we swept an HTLC directly off the remote party's commitment
// transaction, then we'll fall through as we just need to wait until
// the sweep has been confirmed.
case h.htlcResolution.SignedTimeoutTx == nil:
fallthrough

// Finally, if this was an output on our commitment transaction, we'll
// wait for the second-level HTLC output to be spent, and for that
// transaction itself to confirm.
Expand Down

0 comments on commit fb5c245

Please sign in to comment.