@ -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 : anchorSwe epConfTarget ,
ConfTarget : de adli ne,
} ,
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 ( )