@@ -549,7 +549,6 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error {
549549 return err
550550 }
551551
552- // TODO(yy): checkpoint here?
553552 return err
554553}
555554
@@ -573,6 +572,59 @@ func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error {
573572 return h .Checkpoint (h )
574573}
575574
575+ // sweepDirectHtlcOutput sends the direct spend of the HTLC output to the
576+ // sweeper. This is used when the remote party goes on chain, and we're able to
577+ // sweep an HTLC we offered after a timeout. Only the CLTV encumbered outputs
578+ // are resolved via this path.
579+ func (h * htlcTimeoutResolver ) sweepDirectHtlcOutput (immediate bool ) error {
580+ var htlcWitnessType input.StandardWitnessType
581+ if h .isTaproot () {
582+ htlcWitnessType = input .TaprootHtlcOfferedRemoteTimeout
583+ } else {
584+ htlcWitnessType = input .HtlcOfferedRemoteTimeout
585+ }
586+
587+ sweepInput := input .NewCsvInputWithCltv (
588+ & h .htlcResolution .ClaimOutpoint , htlcWitnessType ,
589+ & h .htlcResolution .SweepSignDesc , h .broadcastHeight ,
590+ h .htlcResolution .CsvDelay , h .htlcResolution .Expiry ,
591+ )
592+
593+ // Calculate the budget.
594+ //
595+ // TODO(yy): the budget is twice the output's value, which is needed as
596+ // we don't force sweep the output now. To prevent cascading force
597+ // closes, we use all its output value plus a wallet input as the
598+ // budget. This is a temporary solution until we can optionally cancel
599+ // the incoming HTLC, more details in,
600+ // - https://github.com/lightningnetwork/lnd/issues/7969
601+ budget := calculateBudget (
602+ btcutil .Amount (sweepInput .SignDesc ().Output .Value ), 2 , 0 ,
603+ )
604+
605+ log .Infof ("%T(%x): offering offered remote timeout HTLC output to " +
606+ "sweeper with deadline %v and budget=%v at height=%v" ,
607+ h , h .htlc .RHash [:], h .incomingHTLCExpiryHeight , budget ,
608+ h .broadcastHeight )
609+
610+ _ , err := h .Sweeper .SweepInput (
611+ sweepInput ,
612+ sweep.Params {
613+ Budget : budget ,
614+
615+ // This is an outgoing HTLC, so we want to make sure
616+ // that we sweep it before the incoming HTLC expires.
617+ DeadlineHeight : h .incomingHTLCExpiryHeight ,
618+ Immediate : immediate ,
619+ },
620+ )
621+ if err != nil {
622+ return err
623+ }
624+
625+ return nil
626+ }
627+
576628// spendHtlcOutput handles the initial spend of an HTLC output via the timeout
577629// clause. If this is our local commitment, the second-level timeout TX will be
578630// used to spend the output into the next stage. If this is the remote
@@ -593,8 +645,18 @@ func (h *htlcTimeoutResolver) spendHtlcOutput(
593645 return nil , err
594646 }
595647
596- // If we have no SignDetails, and we haven't already sent the output to
597- // the utxo nursery, then we'll do so now.
648+ // If this is a remote commitment there's no second level timeout txn,
649+ // and we can just send this directly to the sweeper.
650+ case h .htlcResolution .SignedTimeoutTx == nil && ! h .outputIncubating :
651+ if err := h .sweepDirectHtlcOutput (immediate ); err != nil {
652+ log .Errorf ("Sending direct spend to sweeper: %v" , err )
653+
654+ return nil , err
655+ }
656+
657+ // If we have a SignedTimeoutTx but no SignDetails, this is a local
658+ // commitment for a non-anchor channel, so we'll send it to the utxo
659+ // nursery.
598660 case h .htlcResolution .SignDetails == nil && ! h .outputIncubating :
599661 if err := h .sendSecondLevelTxLegacy (); err != nil {
600662 log .Errorf ("Sending timeout tx to nursery: %v" , err )
@@ -701,6 +763,13 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
701763 )
702764
703765 switch {
766+
767+ // If we swept an HTLC directly off the remote party's commitment
768+ // transaction, then we can exit here as there's no second level sweep
769+ // to do.
770+ case h .htlcResolution .SignedTimeoutTx == nil :
771+ break
772+
704773 // If the sweeper is handling the second level transaction, wait for
705774 // the CSV and possible CLTV lock to expire, before sweeping the output
706775 // on the second-level.
@@ -774,6 +843,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
774843 h .htlcResolution .CsvDelay ,
775844 uint32 (commitSpend .SpendingHeight ), h .htlc .RHash ,
776845 )
846+
777847 // Calculate the budget for this sweep.
778848 budget := calculateBudget (
779849 btcutil .Amount (inp .SignDesc ().Output .Value ),
@@ -811,6 +881,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
811881 case h .htlcResolution .SignedTimeoutTx != nil :
812882 log .Infof ("%T(%v): waiting for nursery/sweeper to spend CSV " +
813883 "delayed output" , h , claimOutpoint )
884+
814885 sweepTx , err := waitForSpend (
815886 & claimOutpoint ,
816887 h .htlcResolution .SweepSignDesc .Output .PkScript ,
@@ -877,9 +948,11 @@ func (h *htlcTimeoutResolver) IsResolved() bool {
877948
878949// report returns a report on the resolution state of the contract.
879950func (h * htlcTimeoutResolver ) report () * ContractReport {
880- // If the sign details are nil, the report will be created by handled
881- // by the nursery.
882- if h .htlcResolution .SignDetails == nil {
951+ // If we have a SignedTimeoutTx but no SignDetails, this is a local
952+ // commitment for a non-anchor channel, which was handled by the utxo
953+ // nursery.
954+ if h .htlcResolution .SignDetails == nil && h .
955+ htlcResolution .SignedTimeoutTx != nil {
883956 return nil
884957 }
885958
@@ -899,13 +972,20 @@ func (h *htlcTimeoutResolver) initReport() {
899972 )
900973 }
901974
975+ // If there's no timeout transaction, then we're already effectively in
976+ // level two.
977+ stage := uint32 (1 )
978+ if h .htlcResolution .SignedTimeoutTx == nil {
979+ stage = 2
980+ }
981+
902982 h .currentReport = ContractReport {
903983 Outpoint : h .htlcResolution .ClaimOutpoint ,
904984 Type : ReportOutputOutgoingHtlc ,
905985 Amount : finalAmt ,
906986 MaturityHeight : h .htlcResolution .Expiry ,
907987 LimboBalance : finalAmt ,
908- Stage : 1 ,
988+ Stage : stage ,
909989 }
910990}
911991
0 commit comments