contractcourt: make anchor sweep deadline aware

In this commit, we made the change so that when sweeping anchors for the
commitment transactions, we will be aware of the deadline which is
derived from its HTLC set. It's very likely we will use a much larger
conf target from now on, and save us some sats.
This commit is contained in:
yyforyongyu 2021-05-07 21:09:09 +08:00
parent adddc1442e
commit 391f240ccb
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"math"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -31,8 +32,10 @@ var (
const ( const (
// anchorSweepConfTarget is the conf target used when sweeping // anchorSweepConfTarget is the conf target used when sweeping
// commitment anchors. // commitment anchors. This value is only used when the commitment
anchorSweepConfTarget = 6 // transaction has no valid HTLCs for determining a confirmation
// deadline.
anchorSweepConfTarget = 144
// arbitratorBlockBufferSize is the size of the buffer we give to each // arbitratorBlockBufferSize is the size of the buffer we give to each
// channel arbitrator. // channel arbitrator.
@ -1094,10 +1097,16 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
// anchors from being batched together. // anchors from being batched together.
exclusiveGroup := c.cfg.ShortChanID.ToUint64() exclusiveGroup := c.cfg.ShortChanID.ToUint64()
// TODO: refactor this function in next commit. // sweepWithDeadline is a helper closure that takes an anchor
for _, anchor := range []*lnwallet.AnchorResolution{ // resolution and sweeps it with its corresponding deadline.
anchors.Local, anchors.Remote, anchors.RemotePending, sweepWithDeadline := func(anchor *lnwallet.AnchorResolution,
} { htlcs htlcSet) error {
// Find the deadline for this specific anchor.
deadline, err := c.findCommitmentDeadline(heightHint, htlcs)
if err != nil {
return err
}
log.Debugf("ChannelArbitrator(%v): pre-confirmation sweep of "+ log.Debugf("ChannelArbitrator(%v): pre-confirmation sweep of "+
"anchor of tx %v", c.cfg.ChanPoint, anchor.CommitAnchor) "anchor of tx %v", c.cfg.ChanPoint, anchor.CommitAnchor)
@ -1122,11 +1131,11 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
// Also signal that this is a force sweep, so that the anchor // Also signal that this is a force sweep, so that the anchor
// will be swept even if it isn't economical purely based on the // will be swept even if it isn't economical purely based on the
// anchor value. // anchor value.
_, err := c.cfg.Sweeper.SweepInput( _, err = c.cfg.Sweeper.SweepInput(
&anchorInput, &anchorInput,
sweep.Params{ sweep.Params{
Fee: sweep.FeePreference{ Fee: sweep.FeePreference{
ConfTarget: anchorSweepConfTarget, ConfTarget: deadline,
}, },
Force: true, Force: true,
ExclusiveGroup: &exclusiveGroup, ExclusiveGroup: &exclusiveGroup,
@ -1135,11 +1144,129 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
if err != nil { if err != nil {
return err return err
} }
return nil
}
// Sweep anchors based on different HTLC sets. Notice the HTLC sets may
// differ across commitments, thus their deadline values could vary.
for htlcSet, htlcs := range c.activeHTLCs {
switch {
case htlcSet == LocalHtlcSet && anchors.Local != nil:
err := sweepWithDeadline(anchors.Local, htlcs)
if err != nil {
return err
}
case htlcSet == RemoteHtlcSet && anchors.Remote != nil:
err := sweepWithDeadline(anchors.Remote, htlcs)
if err != nil {
return err
}
case htlcSet == RemotePendingHtlcSet &&
anchors.RemotePending != nil:
err := sweepWithDeadline(anchors.RemotePending, htlcs)
if err != nil {
return err
}
}
} }
return nil return nil
} }
// findCommitmentDeadline finds the deadline (relative block height) for a
// commitment transaction by extracting the minimum CLTV from its HTLCs. From
// our PoV, the deadline is defined to be the smaller of,
// - the least CLTV from outgoing HTLCs, or,
// - the least CLTV from incoming HTLCs if the preimage is available.
//
// Note: when the deadline turns out to be 0 blocks, we will replace it with 1
// block because our fee estimator doesn't allow a 0 conf target. This also
// means we've left behind and should increase our fee to make the transaction
// confirmed asap.
func (c *ChannelArbitrator) findCommitmentDeadline(heightHint uint32,
htlcs htlcSet) (uint32, error) {
deadlineMinHeight := uint32(math.MaxUint32)
// First, iterate through the outgoingHTLCs to find the lowest CLTV
// value.
for _, htlc := range htlcs.outgoingHTLCs {
// Skip if the HTLC is dust.
if htlc.OutputIndex < 0 {
log.Debugf("ChannelArbitrator(%v): skipped deadline "+
"for dust htlc=%x",
c.cfg.ChanPoint, htlc.RHash[:])
continue
}
if htlc.RefundTimeout < deadlineMinHeight {
deadlineMinHeight = htlc.RefundTimeout
}
}
// Then going through the incomingHTLCs, and update the minHeight when
// conditions met.
for _, htlc := range htlcs.incomingHTLCs {
// Skip if the HTLC is dust.
if htlc.OutputIndex < 0 {
log.Debugf("ChannelArbitrator(%v): skipped deadline "+
"for dust htlc=%x",
c.cfg.ChanPoint, htlc.RHash[:])
continue
}
// Since it's an HTLC sent to us, check if we have preimage for
// this HTLC.
preimageAvailable, err := c.isPreimageAvailable(htlc.RHash)
if err != nil {
return 0, err
}
if !preimageAvailable {
continue
}
if htlc.RefundTimeout < deadlineMinHeight {
deadlineMinHeight = htlc.RefundTimeout
}
}
// Calculate the deadline. There are two cases to be handled here,
// - when the deadlineMinHeight never gets updated, which could
// happen when we have no outgoing HTLCs, and, for incoming HTLCs,
// * either we have none, or,
// * none of the HTLCs are preimageAvailable.
// - when our deadlineMinHeight is no greater than the heightHint,
// which means we are behind our schedule.
deadline := deadlineMinHeight - heightHint
switch {
// When we couldn't find a deadline height from our HTLCs, we will fall
// back to the default value.
case deadlineMinHeight == math.MaxUint32:
deadline = anchorSweepConfTarget
// When the deadline is passed, we will fall back to the smallest conf
// target (1 block).
case deadlineMinHeight <= heightHint:
log.Warnf("ChannelArbitrator(%v): deadline is passed with "+
"deadlineMinHeight=%d, heightHint=%d",
c.cfg.ChanPoint, deadlineMinHeight, heightHint)
deadline = 1
}
log.Debugf("ChannelArbitrator(%v): calculated deadline: %d, "+
"using deadlineMinHeight=%d, heightHint=%d",
c.cfg.ChanPoint, deadline, deadlineMinHeight, heightHint)
return deadline, nil
}
// launchResolvers updates the activeResolvers list and starts the resolvers. // launchResolvers updates the activeResolvers list and starts the resolvers.
func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver) { func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver) {
c.activeResolversLock.Lock() c.activeResolversLock.Lock()