@@ -1390,133 +1390,22 @@ func (c *ChannelArbitrator) stateStep(
13901390func (c * ChannelArbitrator ) sweepAnchors (anchors * lnwallet.AnchorResolutions ,
13911391 heightHint uint32 ) error {
13921392
1393- // Use the chan id as the exclusive group. This prevents any of the
1394- // anchors from being batched together.
1395- exclusiveGroup := c .cfg .ShortChanID .ToUint64 ()
1396-
1397- // sweepWithDeadline is a helper closure that takes an anchor
1398- // resolution and sweeps it with its corresponding deadline.
1399- sweepWithDeadline := func (anchor * lnwallet.AnchorResolution ,
1400- htlcs htlcSet , anchorPath string ) error {
1401-
1402- // Find the deadline for this specific anchor.
1403- deadline , value , err := c .findCommitmentDeadlineAndValue (
1404- heightHint , htlcs ,
1405- )
1406- if err != nil {
1407- return err
1408- }
1409-
1410- // If we cannot find a deadline, it means there's no HTLCs at
1411- // stake, which means we can relax our anchor sweeping
1412- // conditions as we don't have any time sensitive outputs to
1413- // sweep. However we need to register the anchor output with the
1414- // sweeper so we are later able to bump the close fee.
1415- if deadline .IsNone () {
1416- log .Infof ("ChannelArbitrator(%v): no HTLCs at stake, " +
1417- "sweeping anchor with default deadline" ,
1418- c .cfg .ChanPoint )
1419- }
1420-
1421- witnessType := input .CommitmentAnchor
1422-
1423- // For taproot channels, we need to use the proper witness
1424- // type.
1425- if txscript .IsPayToTaproot (
1426- anchor .AnchorSignDescriptor .Output .PkScript ,
1427- ) {
1428-
1429- witnessType = input .TaprootAnchorSweepSpend
1430- }
1431-
1432- // Prepare anchor output for sweeping.
1433- anchorInput := input .MakeBaseInput (
1434- & anchor .CommitAnchor ,
1435- witnessType ,
1436- & anchor .AnchorSignDescriptor ,
1437- heightHint ,
1438- & input.TxInfo {
1439- Fee : anchor .CommitFee ,
1440- Weight : anchor .CommitWeight ,
1441- },
1442- )
1443-
1444- // If we have a deadline, we'll use it to calculate the
1445- // deadline height, otherwise default to none.
1446- deadlineDesc := "None"
1447- deadlineHeight := fn .MapOption (func (d int32 ) int32 {
1448- deadlineDesc = fmt .Sprintf ("%d" , d )
1449-
1450- return d + int32 (heightHint )
1451- })(deadline )
1452-
1453- // Calculate the budget based on the value under protection,
1454- // which is the sum of all HTLCs on this commitment subtracted
1455- // by their budgets.
1456- // The anchor output in itself has a small output value of 330
1457- // sats so we also include it in the budget to pay for the
1458- // cpfp transaction.
1459- budget := calculateBudget (
1460- value , c .cfg .Budget .AnchorCPFPRatio ,
1461- c .cfg .Budget .AnchorCPFP ,
1462- ) + AnchorOutputValue
1463-
1464- log .Infof ("ChannelArbitrator(%v): offering anchor from %s " +
1465- "commitment %v to sweeper with deadline=%v, budget=%v" ,
1466- c .cfg .ChanPoint , anchorPath , anchor .CommitAnchor ,
1467- deadlineDesc , budget )
1468-
1469- // Sweep anchor output with a confirmation target fee
1470- // preference. Because this is a cpfp-operation, the anchor
1471- // will only be attempted to sweep when the current fee
1472- // estimate for the confirmation target exceeds the commit fee
1473- // rate.
1474- _ , err = c .cfg .Sweeper .SweepInput (
1475- & anchorInput ,
1476- sweep.Params {
1477- ExclusiveGroup : & exclusiveGroup ,
1478- Budget : budget ,
1479- DeadlineHeight : deadlineHeight ,
1480- },
1481- )
1482- if err != nil {
1483- return err
1484- }
1485-
1486- return nil
1487- }
1488-
14891393 // Update the set of activeHTLCs so that the sweeping routine has an
14901394 // up-to-date view of the set of commitments.
14911395 c .updateActiveHTLCs ()
14921396
1493- // Sweep anchors based on different HTLC sets. Notice the HTLC sets may
1494- // differ across commitments, thus their deadline values could vary.
1495- for htlcSet , htlcs := range c .activeHTLCs {
1496- switch {
1497- case htlcSet == LocalHtlcSet && anchors .Local != nil :
1498- err := sweepWithDeadline (anchors .Local , htlcs , "local" )
1499- if err != nil {
1500- return err
1501- }
1502-
1503- case htlcSet == RemoteHtlcSet && anchors .Remote != nil :
1504- err := sweepWithDeadline (
1505- anchors .Remote , htlcs , "remote" ,
1506- )
1507- if err != nil {
1508- return err
1509- }
1510-
1511- case htlcSet == RemotePendingHtlcSet &&
1512- anchors .RemotePending != nil :
1397+ // Prepare the sweeping requests for all possible versions of
1398+ // commitments.
1399+ sweepReqs , err := c .prepareAnchorSweeps (heightHint , anchors )
1400+ if err != nil {
1401+ return err
1402+ }
15131403
1514- err := sweepWithDeadline (
1515- anchors .RemotePending , htlcs , "remote pending" ,
1516- )
1517- if err != nil {
1518- return err
1519- }
1404+ // Send out the sweeping requests to the sweeper.
1405+ for _ , req := range sweepReqs {
1406+ _ , err = c .cfg .Sweeper .SweepInput (req .input , req .params )
1407+ if err != nil {
1408+ return err
15201409 }
15211410 }
15221411
@@ -2254,7 +2143,7 @@ func (c *ChannelArbitrator) checkRemoteChainActions(
22542143}
22552144
22562145// checkRemoteDiffActions checks the set difference of the HTLCs on the remote
2257- // confirmed commit and remote dangling commit for HTLCS that we need to cancel
2146+ // confirmed commit and remote pending commit for HTLCS that we need to cancel
22582147// back. If we find any HTLCs on the remote pending but not the remote, then
22592148// we'll mark them to be failed immediately.
22602149func (c * ChannelArbitrator ) checkRemoteDiffActions (
@@ -2277,7 +2166,7 @@ func (c *ChannelArbitrator) checkRemoteDiffActions(
22772166 }
22782167
22792168 // With the remote HTLCs assembled, we'll mark any HTLCs only on the
2280- // remote dangling commitment to be failed asap.
2169+ // remote pending commitment to be failed asap.
22812170 actionMap := make (ChainActionMap )
22822171 for _ , htlc := range danglingHTLCs .outgoingHTLCs {
22832172 if _ , ok := remoteHtlcs [htlc .HtlcIndex ]; ok {
@@ -3200,6 +3089,192 @@ func (c *ChannelArbitrator) checkLegacyBreach() (ArbitratorState, error) {
32003089 return StateContractClosed , nil
32013090}
32023091
3092+ // sweepRequest wraps the arguments used when calling `SweepInput`.
3093+ type sweepRequest struct {
3094+ // input is the input to be swept.
3095+ input input.Input
3096+
3097+ // params holds the sweeping parameters.
3098+ params sweep.Params
3099+ }
3100+
3101+ // createSweepRequest creates an anchor sweeping request for a particular
3102+ // version (local/remote/remote pending) of the commitment.
3103+ func (c * ChannelArbitrator ) createSweepRequest (
3104+ anchor * lnwallet.AnchorResolution , htlcs htlcSet , anchorPath string ,
3105+ heightHint uint32 ) (sweepRequest , error ) {
3106+
3107+ // Use the chan id as the exclusive group. This prevents any of the
3108+ // anchors from being batched together.
3109+ exclusiveGroup := c .cfg .ShortChanID .ToUint64 ()
3110+
3111+ // Find the deadline for this specific anchor.
3112+ deadline , value , err := c .findCommitmentDeadlineAndValue (
3113+ heightHint , htlcs ,
3114+ )
3115+ if err != nil {
3116+ return sweepRequest {}, err
3117+ }
3118+
3119+ // If we cannot find a deadline, it means there's no HTLCs at stake,
3120+ // which means we can relax our anchor sweeping conditions as we don't
3121+ // have any time sensitive outputs to sweep. However we need to
3122+ // register the anchor output with the sweeper so we are later able to
3123+ // bump the close fee.
3124+ if deadline .IsNone () {
3125+ log .Infof ("ChannelArbitrator(%v): no HTLCs at stake, " +
3126+ "sweeping anchor with default deadline" ,
3127+ c .cfg .ChanPoint )
3128+ }
3129+
3130+ witnessType := input .CommitmentAnchor
3131+
3132+ // For taproot channels, we need to use the proper witness type.
3133+ if txscript .IsPayToTaproot (
3134+ anchor .AnchorSignDescriptor .Output .PkScript ,
3135+ ) {
3136+
3137+ witnessType = input .TaprootAnchorSweepSpend
3138+ }
3139+
3140+ // Prepare anchor output for sweeping.
3141+ anchorInput := input .MakeBaseInput (
3142+ & anchor .CommitAnchor ,
3143+ witnessType ,
3144+ & anchor .AnchorSignDescriptor ,
3145+ heightHint ,
3146+ & input.TxInfo {
3147+ Fee : anchor .CommitFee ,
3148+ Weight : anchor .CommitWeight ,
3149+ },
3150+ )
3151+
3152+ // If we have a deadline, we'll use it to calculate the deadline
3153+ // height, otherwise default to none.
3154+ deadlineDesc := "None"
3155+ deadlineHeight := fn .MapOption (func (d int32 ) int32 {
3156+ deadlineDesc = fmt .Sprintf ("%d" , d )
3157+
3158+ return d + int32 (heightHint )
3159+ })(deadline )
3160+
3161+ // Calculate the budget based on the value under protection, which is
3162+ // the sum of all HTLCs on this commitment subtracted by their budgets.
3163+ // The anchor output in itself has a small output value of 330 sats so
3164+ // we also include it in the budget to pay for the cpfp transaction.
3165+ budget := calculateBudget (
3166+ value , c .cfg .Budget .AnchorCPFPRatio , c .cfg .Budget .AnchorCPFP ,
3167+ ) + AnchorOutputValue
3168+
3169+ log .Infof ("ChannelArbitrator(%v): offering anchor from %s commitment " +
3170+ "%v to sweeper with deadline=%v, budget=%v" , c .cfg .ChanPoint ,
3171+ anchorPath , anchor .CommitAnchor , deadlineDesc , budget )
3172+
3173+ // Sweep anchor output with a confirmation target fee preference.
3174+ // Because this is a cpfp-operation, the anchor will only be attempted
3175+ // to sweep when the current fee estimate for the confirmation target
3176+ // exceeds the commit fee rate.
3177+ return sweepRequest {
3178+ input : & anchorInput ,
3179+ params : sweep.Params {
3180+ ExclusiveGroup : & exclusiveGroup ,
3181+ Budget : budget ,
3182+ DeadlineHeight : deadlineHeight ,
3183+ },
3184+ }, nil
3185+ }
3186+
3187+ // prepareAnchorSweeps creates a list of requests to be used by the sweeper for
3188+ // all possible commitment versions.
3189+ func (c * ChannelArbitrator ) prepareAnchorSweeps (heightHint uint32 ,
3190+ anchors * lnwallet.AnchorResolutions ) ([]sweepRequest , error ) {
3191+
3192+ // requests holds all the possible anchor sweep requests. We can have
3193+ // up to 3 different versions of commitments (local/remote/remote
3194+ // dangling) to be CPFPed by the anchors.
3195+ requests := make ([]sweepRequest , 0 , 3 )
3196+
3197+ // remotePendingReq holds the request for sweeping the anchor output on
3198+ // the remote pending commitment. It's only set when there's an actual
3199+ // pending remote commitment and it's used to decide whether we need to
3200+ // update the fee budget when sweeping the anchor output on the local
3201+ // commitment.
3202+ remotePendingReq := fn .None [sweepRequest ]()
3203+
3204+ // First we check on the remote pending commitment and optionally
3205+ // create an anchor sweeping request.
3206+ htlcs , ok := c .activeHTLCs [RemotePendingHtlcSet ]
3207+ if ok && anchors .RemotePending != nil {
3208+ req , err := c .createSweepRequest (
3209+ anchors .RemotePending , htlcs , "remote pending" ,
3210+ heightHint ,
3211+ )
3212+ if err != nil {
3213+ return nil , err
3214+ }
3215+
3216+ // Save the request.
3217+ requests = append (requests , req )
3218+
3219+ // Set the optional variable.
3220+ remotePendingReq = fn .Some (req )
3221+ }
3222+
3223+ // Check the local commitment and optionally create an anchor sweeping
3224+ // request. The params used in this request will be influenced by the
3225+ // anchor sweeping request made from the pending remote commitment.
3226+ htlcs , ok = c .activeHTLCs [LocalHtlcSet ]
3227+ if ok && anchors .Local != nil {
3228+ req , err := c .createSweepRequest (
3229+ anchors .Local , htlcs , "local" , heightHint ,
3230+ )
3231+ if err != nil {
3232+ return nil , err
3233+ }
3234+
3235+ // If there's an anchor sweeping request from the pending
3236+ // remote commitment, we will compare its budget against the
3237+ // budget used here and choose the params that has a larger
3238+ // budget. The deadline when choosing the remote pending budget
3239+ // instead of the local one will always be earlier or equal to
3240+ // the local deadline because outgoing HTLCs are resolved on
3241+ // the local commitment first before they are removed from the
3242+ // remote one.
3243+ remotePendingReq .WhenSome (func (s sweepRequest ) {
3244+ if s .params .Budget <= req .params .Budget {
3245+ return
3246+ }
3247+
3248+ log .Infof ("ChannelArbitrator(%v): replaced local " +
3249+ "anchor(%v) sweep params with pending remote " +
3250+ "anchor sweep params, \n old:[%v], \n new:[%v]" ,
3251+ c .cfg .ChanPoint , anchors .Local .CommitAnchor ,
3252+ req .params , s .params )
3253+
3254+ req .params = s .params
3255+ })
3256+
3257+ // Save the request.
3258+ requests = append (requests , req )
3259+ }
3260+
3261+ // Check the remote commitment and create an anchor sweeping request if
3262+ // needed.
3263+ htlcs , ok = c .activeHTLCs [RemoteHtlcSet ]
3264+ if ok && anchors .Remote != nil {
3265+ req , err := c .createSweepRequest (
3266+ anchors .Remote , htlcs , "remote" , heightHint ,
3267+ )
3268+ if err != nil {
3269+ return nil , err
3270+ }
3271+
3272+ requests = append (requests , req )
3273+ }
3274+
3275+ return requests , nil
3276+ }
3277+
32033278// failIncomingDust resolves the incoming dust HTLCs because they do not have
32043279// an output on the commitment transaction and cannot be resolved onchain. We
32053280// mark them as failed here.
0 commit comments