@@ -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.
868939func (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