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:
parent
adddc1442e
commit
391f240ccb
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -31,8 +32,10 @@ var (
|
||||
|
||||
const (
|
||||
// anchorSweepConfTarget is the conf target used when sweeping
|
||||
// commitment anchors.
|
||||
anchorSweepConfTarget = 6
|
||||
// commitment anchors. This value is only used when the commitment
|
||||
// transaction has no valid HTLCs for determining a confirmation
|
||||
// deadline.
|
||||
anchorSweepConfTarget = 144
|
||||
|
||||
// arbitratorBlockBufferSize is the size of the buffer we give to each
|
||||
// channel arbitrator.
|
||||
@ -1094,10 +1097,16 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
|
||||
// anchors from being batched together.
|
||||
exclusiveGroup := c.cfg.ShortChanID.ToUint64()
|
||||
|
||||
// TODO: refactor this function in next commit.
|
||||
for _, anchor := range []*lnwallet.AnchorResolution{
|
||||
anchors.Local, anchors.Remote, anchors.RemotePending,
|
||||
} {
|
||||
// sweepWithDeadline is a helper closure that takes an anchor
|
||||
// resolution and sweeps it with its corresponding deadline.
|
||||
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 "+
|
||||
"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
|
||||
// will be swept even if it isn't economical purely based on the
|
||||
// anchor value.
|
||||
_, err := c.cfg.Sweeper.SweepInput(
|
||||
_, err = c.cfg.Sweeper.SweepInput(
|
||||
&anchorInput,
|
||||
sweep.Params{
|
||||
Fee: sweep.FeePreference{
|
||||
ConfTarget: anchorSweepConfTarget,
|
||||
ConfTarget: deadline,
|
||||
},
|
||||
Force: true,
|
||||
ExclusiveGroup: &exclusiveGroup,
|
||||
@ -1135,11 +1144,129 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver) {
|
||||
c.activeResolversLock.Lock()
|
||||
|
Loading…
Reference in New Issue
Block a user