contractcourt: share deadlines between CPFP anchors and HTLCs

This commit changes how the deadline is calculated for CPFP anchor
sweeping. In order to sweep the second-level HTLCs, we need to first
get the FC tx confirmed. If we use a larger conf target for CPFP, we'd
end up having few blocks to sweep the HTLCs, as these two sweeping txns
share the deadline of the HTLC, as shown below,
```
More aggressive on the CPFP part.
|-CPFP-|-----HTLC-----|

Share the deadlines evenly.
|---CPFP---|---HTLC---|

More aggressive on the HTLC part.
|-----CPFP-----|-HTLC-|
```
In this commit, we decide to share the deadlines evenly as a starting
point so neither side will have a short of deadlines.
This commit is contained in:
yyforyongyu committed 2024-05-18 07:45:09 +08:00
1 parent 2906b8b20c
commit 1470adbed2
2 files changed
+120 -37

No files matched your search

+31 -8
View File
@@ -1436,9 +1436,13 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
// findCommitmentDeadlineAndValue finds the deadline (relative block height) // findCommitmentDeadlineAndValue finds the deadline (relative block height)
// for a commitment transaction by extracting the minimum CLTV from its HTLCs. // for a commitment transaction by extracting the minimum CLTV from its HTLCs.
// From our PoV, the deadline is defined to be the smaller of, // From our PoV, the deadline delta is defined to be the smaller of,
// - the least CLTV from outgoing HTLCs, or, // - half of the least CLTV from outgoing HTLCs' corresponding incoming
// - the least CLTV from incoming HTLCs if the preimage is available. // HTLCs, or,
// - half of the least CLTV from incoming HTLCs if the preimage is available.
//
// We use half of the CTLV value to ensure that we have enough time to sweep
// the second-level HTLCs.
// //
// It also finds the total value that are time-sensitive, which is the sum of // It also finds the total value that are time-sensitive, which is the sum of
// all the outgoing HTLCs plus incoming HTLCs whose preimages are known. It // all the outgoing HTLCs plus incoming HTLCs whose preimages are known. It
@@ -1468,10 +1472,24 @@ func (c *ChannelArbitrator) findCommitmentDeadlineAndValue(heightHint uint32,
} }
value := htlc.Amt.ToSatoshis() value := htlc.Amt.ToSatoshis()
totalValue += value
if htlc.RefundTimeout < deadlineMinHeight { // Find the expiry height for this outgoing HTLC's incoming
deadlineMinHeight = htlc.RefundTimeout // HTLC.
deadlineOpt := c.cfg.FindOutgoingHTLCDeadline(htlc)
// The deadline is default to the current deadlineMinHeight,
// and it's overwritten when it's not none.
deadline := deadlineMinHeight
deadlineOpt.WhenSome(func(d int32) {
deadline = uint32(d)
// We only consider the value is under protection when
// it's time-sensitive.
totalValue += value
})
if deadline < deadlineMinHeight {
deadlineMinHeight = deadline
log.Tracef("ChannelArbitrator(%v): outgoing HTLC has "+ log.Tracef("ChannelArbitrator(%v): outgoing HTLC has "+
"deadline=%v, value=%v", c.cfg.ChanPoint, "deadline=%v, value=%v", c.cfg.ChanPoint,
@@ -1521,7 +1539,7 @@ func (c *ChannelArbitrator) findCommitmentDeadlineAndValue(heightHint uint32,
// * none of the HTLCs are preimageAvailable. // * none of the HTLCs are preimageAvailable.
// - when our deadlineMinHeight is no greater than the heightHint, // - when our deadlineMinHeight is no greater than the heightHint,
// which means we are behind our schedule. // which means we are behind our schedule.
deadline := deadlineMinHeight - heightHint var deadline uint32
switch { switch {
// When we couldn't find a deadline height from our HTLCs, we will fall // When we couldn't find a deadline height from our HTLCs, we will fall
// back to the default value as there's no time pressure here. // back to the default value as there's no time pressure here.
@@ -1535,6 +1553,11 @@ func (c *ChannelArbitrator) findCommitmentDeadlineAndValue(heightHint uint32,
"deadlineMinHeight=%d, heightHint=%d", "deadlineMinHeight=%d, heightHint=%d",
c.cfg.ChanPoint, deadlineMinHeight, heightHint) c.cfg.ChanPoint, deadlineMinHeight, heightHint)
deadline = 1 deadline = 1
// Use half of the deadline delta, and leave the other half to be used
// to sweep the HTLCs.
default:
deadline = (deadlineMinHeight - heightHint) / 2
} }
// Calculate the value left after subtracting the budget used for // Calculate the value left after subtracting the budget used for
@@ -2800,7 +2823,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
// state, so we'll get the most up to date signals to we can // state, so we'll get the most up to date signals to we can
// properly do our job. // properly do our job.
case signalUpdate := <-c.signalUpdates: case signalUpdate := <-c.signalUpdates:
log.Tracef("ChannelArbitrator(%v) got new signal "+ log.Tracef("ChannelArbitrator(%v): got new signal "+
"update!", c.cfg.ChanPoint) "update!", c.cfg.ChanPoint)
// We'll update the ShortChannelID. // We'll update the ShortChannelID.
+89 -29
View File
@@ -2305,20 +2305,22 @@ func TestFindCommitmentDeadlineAndValue(t *testing.T) {
} }
testCases := []struct { testCases := []struct {
name string name string
htlcs htlcSet htlcs htlcSet
err error err error
deadline fn.Option[int32] deadline fn.Option[int32]
expectedBudget btcutil.Amount mockFindOutgoingHTLCDeadline func()
expectedBudget btcutil.Amount
}{ }{
{ {
// When we have no HTLCs, the default value should be // When we have no HTLCs, the default value should be
// used. // used.
name: "use default conf target", name: "use default conf target",
htlcs: htlcSet{}, htlcs: htlcSet{},
err: nil, err: nil,
deadline: fn.None[int32](), deadline: fn.None[int32](),
expectedBudget: 0, mockFindOutgoingHTLCDeadline: func() {},
expectedBudget: 0,
}, },
{ {
// When we have a preimage available in the local HTLC // When we have a preimage available in the local HTLC
@@ -2329,8 +2331,17 @@ func TestFindCommitmentDeadlineAndValue(t *testing.T) {
htlcs: makeHTLCSet(htlcPreimage, htlcLargeExpiry), htlcs: makeHTLCSet(htlcPreimage, htlcLargeExpiry),
err: nil, err: nil,
deadline: fn.Some(int32( deadline: fn.Some(int32(
htlcPreimage.RefundTimeout - heightHint, (htlcPreimage.RefundTimeout - heightHint) / 2,
)), )),
mockFindOutgoingHTLCDeadline: func() {
chanArb.cfg.FindOutgoingHTLCDeadline = func(
htlc channeldb.HTLC) fn.Option[int32] {
return fn.Some(int32(
htlcLargeExpiry.RefundTimeout,
))
}
},
expectedBudget: htlcAmt.ToSatoshis(), expectedBudget: htlcAmt.ToSatoshis(),
}, },
{ {
@@ -2342,8 +2353,18 @@ func TestFindCommitmentDeadlineAndValue(t *testing.T) {
htlcs: makeHTLCSet(htlcSmallExipry, htlcLargeExpiry), htlcs: makeHTLCSet(htlcSmallExipry, htlcLargeExpiry),
err: nil, err: nil,
deadline: fn.Some(int32( deadline: fn.Some(int32(
htlcLargeExpiry.RefundTimeout - heightHint, (htlcLargeExpiry.RefundTimeout -
heightHint) / 2,
)), )),
mockFindOutgoingHTLCDeadline: func() {
chanArb.cfg.FindOutgoingHTLCDeadline = func(
htlc channeldb.HTLC) fn.Option[int32] {
return fn.Some(int32(
htlcLargeExpiry.RefundTimeout,
))
}
},
expectedBudget: htlcAmt.ToSatoshis() / 2, expectedBudget: htlcAmt.ToSatoshis() / 2,
}, },
{ {
@@ -2354,18 +2375,36 @@ func TestFindCommitmentDeadlineAndValue(t *testing.T) {
htlcs: makeHTLCSet(htlcPreimage, htlcDust), htlcs: makeHTLCSet(htlcPreimage, htlcDust),
err: nil, err: nil,
deadline: fn.Some(int32( deadline: fn.Some(int32(
htlcPreimage.RefundTimeout - heightHint, (htlcPreimage.RefundTimeout - heightHint) / 2,
)), )),
mockFindOutgoingHTLCDeadline: func() {
chanArb.cfg.FindOutgoingHTLCDeadline = func(
htlc channeldb.HTLC) fn.Option[int32] {
return fn.Some(int32(
htlcDust.RefundTimeout,
))
}
},
expectedBudget: htlcAmt.ToSatoshis() / 2, expectedBudget: htlcAmt.ToSatoshis() / 2,
}, },
{ {
// When we've reached our deadline, use conf target of // When we've reached our deadline, use conf target of
// 1 as our deadline. And the value left should be // 1 as our deadline. And the value left should be
// htlcAmt. // htlcAmt.
name: "use conf target 1", name: "use conf target 1",
htlcs: makeHTLCSet(htlcPreimage, htlcExpired), htlcs: makeHTLCSet(htlcPreimage, htlcExpired),
err: nil, err: nil,
deadline: fn.Some(int32(1)), deadline: fn.Some(int32(1)),
mockFindOutgoingHTLCDeadline: func() {
chanArb.cfg.FindOutgoingHTLCDeadline = func(
htlc channeldb.HTLC) fn.Option[int32] {
return fn.Some(int32(
htlcExpired.RefundTimeout,
))
}
},
expectedBudget: htlcAmt.ToSatoshis(), expectedBudget: htlcAmt.ToSatoshis(),
}, },
} }
@@ -2373,7 +2412,9 @@ func TestFindCommitmentDeadlineAndValue(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() // Mock the method `FindOutgoingHTLCDeadline`.
tc.mockFindOutgoingHTLCDeadline()
deadline, budget, err := chanArb. deadline, budget, err := chanArb.
findCommitmentDeadlineAndValue( findCommitmentDeadlineAndValue(
heightHint, tc.htlcs, heightHint, tc.htlcs,
@@ -2412,31 +2453,35 @@ func TestSweepAnchors(t *testing.T) {
chanArbCtx.chanArb.blocks <- int32(heightHint) chanArbCtx.chanArb.blocks <- int32(heightHint)
htlcIndexBase := uint64(99) htlcIndexBase := uint64(99)
htlcExpiryBase := heightHint + uint32(10) deadlineDelta := uint32(10)
htlcAmt := lnwire.MilliSatoshi(1_000_000) htlcAmt := lnwire.MilliSatoshi(1_000_000)
// Create three testing HTLCs. // Create three testing HTLCs.
htlcDust := channeldb.HTLC{ htlcDust := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 1, HtlcIndex: htlcIndexBase + 1,
RefundTimeout: htlcExpiryBase + 1, RefundTimeout: heightHint + 1,
OutputIndex: -1, OutputIndex: -1,
} }
deadlinePreimageDelta := deadlineDelta + 2
htlcWithPreimage := channeldb.HTLC{ htlcWithPreimage := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 2, HtlcIndex: htlcIndexBase + 2,
RefundTimeout: htlcExpiryBase + 2, RefundTimeout: heightHint + deadlinePreimageDelta,
RHash: rHash, RHash: rHash,
Amt: htlcAmt, Amt: htlcAmt,
} }
deadlineSmallDelta := deadlineDelta + 4
htlcSmallExipry := channeldb.HTLC{ htlcSmallExipry := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 3, HtlcIndex: htlcIndexBase + 3,
RefundTimeout: htlcExpiryBase + 3, RefundTimeout: heightHint + deadlineSmallDelta,
Amt: htlcAmt, Amt: htlcAmt,
} }
// Setup our local HTLC set such that we will use the HTLC's CLTV from // Setup our local HTLC set such that we will use the HTLC's CLTV from
// the incoming HTLC set. // the incoming HTLC set.
expectedLocalDeadline := htlcWithPreimage.RefundTimeout expectedLocalDeadline := heightHint + deadlinePreimageDelta/2
chanArb.activeHTLCs[LocalHtlcSet] = htlcSet{ chanArb.activeHTLCs[LocalHtlcSet] = htlcSet{
incomingHTLCs: map[uint64]channeldb.HTLC{ incomingHTLCs: map[uint64]channeldb.HTLC{
htlcWithPreimage.HtlcIndex: htlcWithPreimage, htlcWithPreimage.HtlcIndex: htlcWithPreimage,
@@ -2475,7 +2520,7 @@ func TestSweepAnchors(t *testing.T) {
// Setup out pending remote HTLC set such that we will use the HTLC's // Setup out pending remote HTLC set such that we will use the HTLC's
// CLTV from the outgoing HTLC set. // CLTV from the outgoing HTLC set.
expectedPendingDeadline := htlcSmallExipry.RefundTimeout expectedPendingDeadline := heightHint + deadlineSmallDelta/2
chanArb.activeHTLCs[RemotePendingHtlcSet] = htlcSet{ chanArb.activeHTLCs[RemotePendingHtlcSet] = htlcSet{
incomingHTLCs: map[uint64]channeldb.HTLC{ incomingHTLCs: map[uint64]channeldb.HTLC{
htlcDust.HtlcIndex: htlcDust, htlcDust.HtlcIndex: htlcDust,
@@ -2493,6 +2538,18 @@ func TestSweepAnchors(t *testing.T) {
}, },
} }
// Mock FindOutgoingHTLCDeadline so the pending remote's outgoing HTLC
// returns the small expiry value.
chanArb.cfg.FindOutgoingHTLCDeadline = func(
htlc channeldb.HTLC) fn.Option[int32] {
if htlc.RHash != htlcSmallExipry.RHash {
return fn.None[int32]()
}
return fn.Some(int32(htlcSmallExipry.RefundTimeout))
}
// Create AnchorResolutions. // Create AnchorResolutions.
anchors := &lnwallet.AnchorResolutions{ anchors := &lnwallet.AnchorResolutions{
Local: &lnwallet.AnchorResolution{ Local: &lnwallet.AnchorResolution{
@@ -2599,17 +2656,20 @@ func TestChannelArbitratorAnchors(t *testing.T) {
htlcAmt := lnwire.MilliSatoshi(1_000_000) htlcAmt := lnwire.MilliSatoshi(1_000_000)
// Create testing HTLCs. // Create testing HTLCs.
htlcExpiryBase := heightHint + uint32(10) deadlineDelta := uint32(10)
deadlinePreimageDelta := deadlineDelta + 2
htlcWithPreimage := channeldb.HTLC{ htlcWithPreimage := channeldb.HTLC{
HtlcIndex: 99, HtlcIndex: 99,
RefundTimeout: htlcExpiryBase + 2, RefundTimeout: heightHint + deadlinePreimageDelta,
RHash: rHash, RHash: rHash,
Incoming: true, Incoming: true,
Amt: htlcAmt, Amt: htlcAmt,
} }
deadlineHTLCdelta := deadlineDelta + 3
htlc := channeldb.HTLC{ htlc := channeldb.HTLC{
HtlcIndex: 100, HtlcIndex: 100,
RefundTimeout: htlcExpiryBase + 3, RefundTimeout: heightHint + deadlineHTLCdelta,
Amt: htlcAmt, Amt: htlcAmt,
} }
@@ -2755,11 +2815,11 @@ func TestChannelArbitratorAnchors(t *testing.T) {
// to htlcWithPreimage's CLTV. // to htlcWithPreimage's CLTV.
require.Equal(t, 2, len(chanArbCtx.sweeper.deadlines)) require.Equal(t, 2, len(chanArbCtx.sweeper.deadlines))
require.EqualValues(t, require.EqualValues(t,
htlcWithPreimage.RefundTimeout, heightHint+deadlinePreimageDelta/2,
chanArbCtx.sweeper.deadlines[0], chanArbCtx.sweeper.deadlines[0],
) )
require.EqualValues(t, require.EqualValues(t,
htlcWithPreimage.RefundTimeout, heightHint+deadlinePreimageDelta/2,
chanArbCtx.sweeper.deadlines[1], chanArbCtx.sweeper.deadlines[1],
) )
} }