Skip to content

Commit 7aa9ade

Browse files
committed
contratcourt: dont consider dust htlc for anchor sweep
Now that we cancel dust htlcs prematurely even before the commitment tx is confirmed we don't consider dust htlcs when creating the cpfp transaction.
1 parent 8ed8665 commit 7aa9ade

File tree

2 files changed

+312
-127
lines changed

2 files changed

+312
-127
lines changed

contractcourt/channel_arbitrator.go

Lines changed: 199 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,133 +1390,22 @@ func (c *ChannelArbitrator) stateStep(
13901390
func (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.
22602149
func (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, \nold:[%v], \nnew:[%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

Comments
 (0)