contractcourt: calculate value left when searching for commit deadline

This commit changes `findCommitmentDeadline` to
`findCommitmentDeadlineAndValue` to calculate the value left from all
the time-sensitive HTLCs after subtracting their budgets. This value is
then used to calculate the budget to be used when sweeping the anchor
output.
This commit is contained in:
yyforyongyu 2024-03-19 11:29:12 +08:00
parent cab180a52e
commit f4035ade05
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
2 changed files with 108 additions and 52 deletions

View File

@ -1321,11 +1321,18 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
htlcs htlcSet, anchorPath string) error { htlcs htlcSet, anchorPath string) error {
// Find the deadline for this specific anchor. // Find the deadline for this specific anchor.
deadline, err := c.findCommitmentDeadline(heightHint, htlcs) deadlineOpt, _, err := c.findCommitmentDeadlineAndValue(
heightHint, htlcs,
)
if err != nil { if err != nil {
return err return err
} }
deadline := uint32(1)
deadlineOpt.WhenSome(func(d int32) {
deadline = uint32(d)
})
// Create a force flag that's used to indicate whether we // Create a force flag that's used to indicate whether we
// should force sweeping this anchor. // should force sweeping this anchor.
var force bool var force bool
@ -1426,20 +1433,26 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
return nil return nil
} }
// findCommitmentDeadline finds the deadline (relative block height) for a // findCommitmentDeadlineAndValue finds the deadline (relative block height)
// commitment transaction by extracting the minimum CLTV from its HTLCs. From // for a commitment transaction by extracting the minimum CLTV from its HTLCs.
// our PoV, the deadline is defined to be the smaller of, // From our PoV, the deadline is defined to be the smaller of,
// - the least CLTV from outgoing HTLCs, or, // - the least CLTV from outgoing HTLCs, or,
// - the least CLTV from incoming HTLCs if the preimage is available. // - 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 // 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
// then returns the value left after subtracting the budget used for sweeping
// the time-sensitive HTLCs.
//
// 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 // 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 // means we've left behind and should increase our fee to make the transaction
// confirmed asap. // confirmed asap.
func (c *ChannelArbitrator) findCommitmentDeadline(heightHint uint32, func (c *ChannelArbitrator) findCommitmentDeadlineAndValue(heightHint uint32,
htlcs htlcSet) (uint32, error) { htlcs htlcSet) (fn.Option[int32], btcutil.Amount, error) {
deadlineMinHeight := uint32(math.MaxUint32) deadlineMinHeight := uint32(math.MaxUint32)
totalValue := btcutil.Amount(0)
// First, iterate through the outgoingHTLCs to find the lowest CLTV // First, iterate through the outgoingHTLCs to find the lowest CLTV
// value. // value.
@ -1453,11 +1466,15 @@ func (c *ChannelArbitrator) findCommitmentDeadline(heightHint uint32,
continue continue
} }
value := htlc.Amt.ToSatoshis()
totalValue += value
if htlc.RefundTimeout < deadlineMinHeight { if htlc.RefundTimeout < deadlineMinHeight {
deadlineMinHeight = htlc.RefundTimeout deadlineMinHeight = htlc.RefundTimeout
log.Tracef("ChannelArbitrator(%v): outgoing HTLC has "+ log.Tracef("ChannelArbitrator(%v): outgoing HTLC has "+
"deadline: %v", c.cfg.ChanPoint, "deadline=%v, value=%v", c.cfg.ChanPoint,
deadlineMinHeight) deadlineMinHeight, value)
} }
} }
@ -1477,18 +1494,22 @@ func (c *ChannelArbitrator) findCommitmentDeadline(heightHint uint32,
// this HTLC. // this HTLC.
preimageAvailable, err := c.isPreimageAvailable(htlc.RHash) preimageAvailable, err := c.isPreimageAvailable(htlc.RHash)
if err != nil { if err != nil {
return 0, err return fn.None[int32](), 0, err
} }
if !preimageAvailable { if !preimageAvailable {
continue continue
} }
value := htlc.Amt.ToSatoshis()
totalValue += value
if htlc.RefundTimeout < deadlineMinHeight { if htlc.RefundTimeout < deadlineMinHeight {
deadlineMinHeight = htlc.RefundTimeout deadlineMinHeight = htlc.RefundTimeout
log.Tracef("ChannelArbitrator(%v): incoming HTLC has "+ log.Tracef("ChannelArbitrator(%v): incoming HTLC has "+
"deadline: %v", c.cfg.ChanPoint, "deadline=%v, amt=%v", c.cfg.ChanPoint,
deadlineMinHeight) deadlineMinHeight, value)
} }
} }
@ -1502,9 +1523,9 @@ func (c *ChannelArbitrator) findCommitmentDeadline(heightHint uint32,
deadline := deadlineMinHeight - heightHint deadline := deadlineMinHeight - heightHint
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. // back to the default value as there's no time pressure here.
case deadlineMinHeight == math.MaxUint32: case deadlineMinHeight == math.MaxUint32:
deadline = anchorSweepConfTarget return fn.None[int32](), 0, nil
// When the deadline is passed, we will fall back to the smallest conf // When the deadline is passed, we will fall back to the smallest conf
// target (1 block). // target (1 block).
@ -1515,11 +1536,19 @@ func (c *ChannelArbitrator) findCommitmentDeadline(heightHint uint32,
deadline = 1 deadline = 1
} }
log.Debugf("ChannelArbitrator(%v): calculated deadline: %d, "+ // Calculate the value left after subtracting the budget used for
"using deadlineMinHeight=%d, heightHint=%d", // sweeping the time-sensitive HTLCs.
c.cfg.ChanPoint, deadline, deadlineMinHeight, heightHint) valueLeft := totalValue - calculateBudget(
totalValue, c.cfg.Budget.DeadlineHTLCRatio,
c.cfg.Budget.DeadlineHTLC,
)
return deadline, nil log.Debugf("ChannelArbitrator(%v): calculated valueLeft=%v, "+
"deadline=%d, using deadlineMinHeight=%d, heightHint=%d",
c.cfg.ChanPoint, valueLeft, deadline, deadlineMinHeight,
heightHint)
return fn.Some(int32(deadline)), valueLeft, nil
} }
// launchResolvers updates the activeResolvers list and starts the resolvers. // launchResolvers updates the activeResolvers list and starts the resolvers.

View File

@ -2226,9 +2226,10 @@ func TestRemoteCloseInitiator(t *testing.T) {
} }
} }
// TestFindCommitmentDeadline tests the logic used to determine confirmation // TestFindCommitmentDeadlineAndValue tests the logic used to determine
// deadline is implemented as expected. // confirmation deadline and total time-sensitive value is implemented as
func TestFindCommitmentDeadline(t *testing.T) { // expected.
func TestFindCommitmentDeadlineAndValue(t *testing.T) {
// Create a testing channel arbitrator. // Create a testing channel arbitrator.
log := &mockArbitratorLog{ log := &mockArbitratorLog{
state: StateDefault, state: StateDefault,
@ -2251,29 +2252,36 @@ func TestFindCommitmentDeadline(t *testing.T) {
heightHint := uint32(1000) heightHint := uint32(1000)
htlcExpiryBase := heightHint + uint32(10) htlcExpiryBase := heightHint + uint32(10)
htlcAmt := lnwire.MilliSatoshi(1_000_000)
// Create four testing HTLCs. // Create four testing HTLCs.
htlcDust := channeldb.HTLC{ htlcDust := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 1, HtlcIndex: htlcIndexBase + 1,
RefundTimeout: htlcExpiryBase + 1, RefundTimeout: htlcExpiryBase + 1,
OutputIndex: -1, OutputIndex: -1,
Amt: htlcAmt,
} }
htlcSmallExipry := channeldb.HTLC{ htlcSmallExipry := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 2, HtlcIndex: htlcIndexBase + 2,
RefundTimeout: htlcExpiryBase + 2, RefundTimeout: htlcExpiryBase + 2,
Amt: htlcAmt,
} }
htlcPreimage := channeldb.HTLC{ htlcPreimage := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 3, HtlcIndex: htlcIndexBase + 3,
RefundTimeout: htlcExpiryBase + 3, RefundTimeout: htlcExpiryBase + 3,
RHash: rHash, RHash: rHash,
Amt: htlcAmt,
} }
htlcLargeExpiry := channeldb.HTLC{ htlcLargeExpiry := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 4, HtlcIndex: htlcIndexBase + 4,
RefundTimeout: htlcExpiryBase + 100, RefundTimeout: htlcExpiryBase + 100,
Amt: htlcAmt,
} }
htlcExpired := channeldb.HTLC{ htlcExpired := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 5, HtlcIndex: htlcIndexBase + 5,
RefundTimeout: heightHint, RefundTimeout: heightHint,
Amt: htlcAmt,
} }
makeHTLCSet := func(incoming, outgoing channeldb.HTLC) htlcSet { makeHTLCSet := func(incoming, outgoing channeldb.HTLC) htlcSet {
@ -2291,7 +2299,8 @@ func TestFindCommitmentDeadline(t *testing.T) {
name string name string
htlcs htlcSet htlcs htlcSet
err error err error
deadline uint32 deadline fn.Option[int32]
expectedBudget btcutil.Amount
}{ }{
{ {
// When we have no HTLCs, the default value should be // When we have no HTLCs, the default value should be
@ -2299,40 +2308,56 @@ func TestFindCommitmentDeadline(t *testing.T) {
name: "use default conf target", name: "use default conf target",
htlcs: htlcSet{}, htlcs: htlcSet{},
err: nil, err: nil,
deadline: anchorSweepConfTarget, deadline: fn.None[int32](),
expectedBudget: 0,
}, },
{ {
// When we have a preimage available in the local HTLC // When we have a preimage available in the local HTLC
// set, its CLTV should be used. // set, its CLTV should be used. And the value left
// should be the sum of the HTLCs minus their budgets,
// which is exactly htlcAmt.
name: "use htlc with preimage available", name: "use htlc with preimage available",
htlcs: makeHTLCSet(htlcPreimage, htlcLargeExpiry), htlcs: makeHTLCSet(htlcPreimage, htlcLargeExpiry),
err: nil, err: nil,
deadline: htlcPreimage.RefundTimeout - heightHint, deadline: fn.Some(int32(
htlcPreimage.RefundTimeout - heightHint,
)),
expectedBudget: htlcAmt.ToSatoshis(),
}, },
{ {
// When the HTLC in the local set is not preimage // When the HTLC in the local set is not preimage
// available, we should not use its CLTV even its value // available, we should not use its CLTV even its value
// is smaller. // is smaller. And the value left should be half of
// htlcAmt.
name: "use htlc with no preimage available", name: "use htlc with no preimage available",
htlcs: makeHTLCSet(htlcSmallExipry, htlcLargeExpiry), htlcs: makeHTLCSet(htlcSmallExipry, htlcLargeExpiry),
err: nil, err: nil,
deadline: htlcLargeExpiry.RefundTimeout - heightHint, deadline: fn.Some(int32(
htlcLargeExpiry.RefundTimeout - heightHint,
)),
expectedBudget: htlcAmt.ToSatoshis() / 2,
}, },
{ {
// When we have dust HTLCs, their CLTVs should NOT be // When we have dust HTLCs, their CLTVs should NOT be
// used even the values are smaller. // used even the values are smaller. And the value left
// should be half of htlcAmt.
name: "ignore dust HTLCs", name: "ignore dust HTLCs",
htlcs: makeHTLCSet(htlcPreimage, htlcDust), htlcs: makeHTLCSet(htlcPreimage, htlcDust),
err: nil, err: nil,
deadline: htlcPreimage.RefundTimeout - heightHint, deadline: fn.Some(int32(
htlcPreimage.RefundTimeout - heightHint,
)),
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. // 1 as our deadline. And the value left should be
// htlcAmt.
name: "use conf target 1", name: "use conf target 1",
htlcs: makeHTLCSet(htlcPreimage, htlcExpired), htlcs: makeHTLCSet(htlcPreimage, htlcExpired),
err: nil, err: nil,
deadline: 1, deadline: fn.Some(int32(1)),
expectedBudget: htlcAmt.ToSatoshis(),
}, },
} }
@ -2340,12 +2365,14 @@ func TestFindCommitmentDeadline(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
deadline, err := chanArb.findCommitmentDeadline( deadline, budget, err := chanArb.
findCommitmentDeadlineAndValue(
heightHint, tc.htlcs, heightHint, tc.htlcs,
) )
require.Equal(t, tc.err, err) require.Equal(t, tc.err, err)
require.Equal(t, tc.deadline, deadline) require.Equal(t, tc.deadline, deadline)
require.Equal(t, tc.expectedBudget, budget)
}) })
} }
} }