Skip to content

Commit 84c91f7

Browse files
authored
Merge pull request #9062 from Roasbeef/htlc-resolution-sweeper
contractcourt: use the sweeper for HTLC offered remote timeout resolu…
2 parents 611852f + 903c8fc commit 84c91f7

File tree

2 files changed

+358
-244
lines changed

2 files changed

+358
-244
lines changed

contractcourt/htlc_timeout_resolver.go

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,6 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error {
538538
return err
539539
}
540540

541-
// TODO(yy): checkpoint here?
542541
return err
543542
}
544543

@@ -562,6 +561,59 @@ func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error {
562561
return h.Checkpoint(h)
563562
}
564563

564+
// sweepDirectHtlcOutput sends the direct spend of the HTLC output to the
565+
// sweeper. This is used when the remote party goes on chain, and we're able to
566+
// sweep an HTLC we offered after a timeout. Only the CLTV encumbered outputs
567+
// are resolved via this path.
568+
func (h *htlcTimeoutResolver) sweepDirectHtlcOutput(immediate bool) error {
569+
var htlcWitnessType input.StandardWitnessType
570+
if h.isTaproot() {
571+
htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout
572+
} else {
573+
htlcWitnessType = input.HtlcOfferedRemoteTimeout
574+
}
575+
576+
sweepInput := input.NewCsvInputWithCltv(
577+
&h.htlcResolution.ClaimOutpoint, htlcWitnessType,
578+
&h.htlcResolution.SweepSignDesc, h.broadcastHeight,
579+
h.htlcResolution.CsvDelay, h.htlcResolution.Expiry,
580+
)
581+
582+
// Calculate the budget.
583+
//
584+
// TODO(yy): the budget is twice the output's value, which is needed as
585+
// we don't force sweep the output now. To prevent cascading force
586+
// closes, we use all its output value plus a wallet input as the
587+
// budget. This is a temporary solution until we can optionally cancel
588+
// the incoming HTLC, more details in,
589+
// - https://github.com/lightningnetwork/lnd/issues/7969
590+
budget := calculateBudget(
591+
btcutil.Amount(sweepInput.SignDesc().Output.Value), 2, 0,
592+
)
593+
594+
log.Infof("%T(%x): offering offered remote timeout HTLC output to "+
595+
"sweeper with deadline %v and budget=%v at height=%v",
596+
h, h.htlc.RHash[:], h.incomingHTLCExpiryHeight, budget,
597+
h.broadcastHeight)
598+
599+
_, err := h.Sweeper.SweepInput(
600+
sweepInput,
601+
sweep.Params{
602+
Budget: budget,
603+
604+
// This is an outgoing HTLC, so we want to make sure
605+
// that we sweep it before the incoming HTLC expires.
606+
DeadlineHeight: h.incomingHTLCExpiryHeight,
607+
Immediate: immediate,
608+
},
609+
)
610+
if err != nil {
611+
return err
612+
}
613+
614+
return nil
615+
}
616+
565617
// spendHtlcOutput handles the initial spend of an HTLC output via the timeout
566618
// clause. If this is our local commitment, the second-level timeout TX will be
567619
// used to spend the output into the next stage. If this is the remote
@@ -582,8 +634,18 @@ func (h *htlcTimeoutResolver) spendHtlcOutput(
582634
return nil, err
583635
}
584636

585-
// If we have no SignDetails, and we haven't already sent the output to
586-
// the utxo nursery, then we'll do so now.
637+
// If this is a remote commitment there's no second level timeout txn,
638+
// and we can just send this directly to the sweeper.
639+
case h.htlcResolution.SignedTimeoutTx == nil && !h.outputIncubating:
640+
if err := h.sweepDirectHtlcOutput(immediate); err != nil {
641+
log.Errorf("Sending direct spend to sweeper: %v", err)
642+
643+
return nil, err
644+
}
645+
646+
// If we have a SignedTimeoutTx but no SignDetails, this is a local
647+
// commitment for a non-anchor channel, so we'll send it to the utxo
648+
// nursery.
587649
case h.htlcResolution.SignDetails == nil && !h.outputIncubating:
588650
if err := h.sendSecondLevelTxLegacy(); err != nil {
589651
log.Errorf("Sending timeout tx to nursery: %v", err)
@@ -690,6 +752,13 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
690752
)
691753

692754
switch {
755+
756+
// If we swept an HTLC directly off the remote party's commitment
757+
// transaction, then we can exit here as there's no second level sweep
758+
// to do.
759+
case h.htlcResolution.SignedTimeoutTx == nil:
760+
break
761+
693762
// If the sweeper is handling the second level transaction, wait for
694763
// the CSV and possible CLTV lock to expire, before sweeping the output
695764
// on the second-level.
@@ -763,6 +832,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
763832
h.htlcResolution.CsvDelay,
764833
uint32(commitSpend.SpendingHeight), h.htlc.RHash,
765834
)
835+
766836
// Calculate the budget for this sweep.
767837
budget := calculateBudget(
768838
btcutil.Amount(inp.SignDesc().Output.Value),
@@ -800,6 +870,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
800870
case h.htlcResolution.SignedTimeoutTx != nil:
801871
log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
802872
"delayed output", h, claimOutpoint)
873+
803874
sweepTx, err := waitForSpend(
804875
&claimOutpoint,
805876
h.htlcResolution.SweepSignDesc.Output.PkScript,
@@ -866,9 +937,11 @@ func (h *htlcTimeoutResolver) IsResolved() bool {
866937

867938
// report returns a report on the resolution state of the contract.
868939
func (h *htlcTimeoutResolver) report() *ContractReport {
869-
// If the sign details are nil, the report will be created by handled
870-
// by the nursery.
871-
if h.htlcResolution.SignDetails == nil {
940+
// If we have a SignedTimeoutTx but no SignDetails, this is a local
941+
// commitment for a non-anchor channel, which was handled by the utxo
942+
// nursery.
943+
if h.htlcResolution.SignDetails == nil && h.
944+
htlcResolution.SignedTimeoutTx != nil {
872945
return nil
873946
}
874947

@@ -888,13 +961,20 @@ func (h *htlcTimeoutResolver) initReport() {
888961
)
889962
}
890963

964+
// If there's no timeout transaction, then we're already effectively in
965+
// level two.
966+
stage := uint32(1)
967+
if h.htlcResolution.SignedTimeoutTx == nil {
968+
stage = 2
969+
}
970+
891971
h.currentReport = ContractReport{
892972
Outpoint: h.htlcResolution.ClaimOutpoint,
893973
Type: ReportOutputOutgoingHtlc,
894974
Amount: finalAmt,
895975
MaturityHeight: h.htlcResolution.Expiry,
896976
LimboBalance: finalAmt,
897-
Stage: 1,
977+
Stage: stage,
898978
}
899979
}
900980

0 commit comments

Comments
 (0)