contractcourt: specify deadline and budget for anchor output

This commit is contained in:
yyforyongyu 2024-03-19 11:31:28 +08:00
parent f4035ade05
commit 6f0c2b5bab
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
4 changed files with 91 additions and 51 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/sweep"
)
@ -118,6 +119,14 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
Fee: sweep.FeeEstimateInfo{
FeeRate: relayFeeRate,
},
// For normal anchor sweeping, the budget is 330 sats.
Budget: btcutil.Amount(
anchorInput.SignDesc().Output.Value,
),
// There's no rush to sweep the anchor, so we use a nil
// deadline here.
DeadlineHeight: fn.None[int32](),
},
)
if err != nil {

View File

@ -37,12 +37,6 @@ var (
)
const (
// anchorSweepConfTarget is the conf target used when sweeping
// 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.
arbitratorBlockBufferSize = 20
@ -1321,36 +1315,23 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
htlcs htlcSet, anchorPath string) error {
// Find the deadline for this specific anchor.
deadlineOpt, _, err := c.findCommitmentDeadlineAndValue(
deadline, value, err := c.findCommitmentDeadlineAndValue(
heightHint, htlcs,
)
if err != nil {
return err
}
deadline := uint32(1)
deadlineOpt.WhenSome(func(d int32) {
deadline = uint32(d)
})
// If we cannot find a deadline, it means there's no HTLCs at
// stake, which means we can relax our anchor sweeping as we
// don't have any time sensitive outputs to sweep.
if deadline.IsNone() {
log.Infof("ChannelArbitrator(%v): no HTLCs at stake, "+
"skipped anchor CPFP", c.cfg.ChanPoint)
// Create a force flag that's used to indicate whether we
// should force sweeping this anchor.
var force bool
// Check the deadline against the default value. If it's less
// than the default value of 144, it means there is a deadline
// and we will perform a CPFP for this commitment tx.
if deadline < anchorSweepConfTarget {
// 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.
force = true
return nil
}
log.Debugf("ChannelArbitrator(%v): pre-confirmation sweep of "+
"anchor of %s commit tx %v, force=%v", c.cfg.ChanPoint,
anchorPath, anchor.CommitAnchor, force)
witnessType := input.CommitmentAnchor
// For taproot channels, we need to use the proper witness
@ -1374,6 +1355,28 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
},
)
// If we have a deadline, we'll use it to calculate the
// deadline height, otherwise default to none.
deadlineDesc := "None"
deadlineHeight := fn.MapOption(func(d int32) int32 {
deadlineDesc = fmt.Sprintf("%d", d)
return d + int32(heightHint)
})(deadline)
// Calculate the budget based on the value under protection,
// which is the sum of all HTLCs on this commitment subtracted
// by their budgets.
budget := calculateBudget(
value, c.cfg.Budget.AnchorCPFPRatio,
c.cfg.Budget.AnchorCPFP,
)
log.Infof("ChannelArbitrator(%v): offering anchor from %s "+
"commitment %v to sweeper with deadline=%v, budget=%v",
c.cfg.ChanPoint, anchorPath, anchor.CommitAnchor,
deadlineDesc, budget)
// Sweep anchor output with a confirmation target fee
// preference. Because this is a cpfp-operation, the anchor
// will only be attempted to sweep when the current fee
@ -1382,11 +1385,9 @@ func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
_, err = c.cfg.Sweeper.SweepInput(
&anchorInput,
sweep.Params{
Fee: sweep.FeeEstimateInfo{
ConfTarget: deadline,
},
Force: force,
ExclusiveGroup: &exclusiveGroup,
Budget: budget,
DeadlineHeight: deadlineHeight,
},
)
if err != nil {
@ -2138,6 +2139,20 @@ func (c *ChannelArbitrator) checkRemoteDiffActions(height uint32,
continue
}
preimageAvailable, err := c.isPreimageAvailable(htlc.RHash)
if err != nil {
log.Errorf("ChannelArbitrator(%v): failed to query "+
"preimage for dangling htlc=%x from remote "+
"commitments diff", c.cfg.ChanPoint,
htlc.RHash[:])
continue
}
if preimageAvailable {
continue
}
actionMap[HtlcFailNowAction] = append(
actionMap[HtlcFailNowAction], htlc,
)

View File

@ -392,6 +392,8 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
return nil
},
Budget: *DefaultBudgetConfig(),
PreimageDB: newMockWitnessBeacon(),
Registry: &mockRegistry{},
}
// We'll use the resolvedChan to synchronize on call to
@ -2405,6 +2407,8 @@ func TestSweepAnchors(t *testing.T) {
htlcIndexBase := uint64(99)
htlcExpiryBase := heightHint + uint32(10)
htlcAmt := lnwire.MilliSatoshi(1_000_000)
// Create three testing HTLCs.
htlcDust := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 1,
@ -2415,15 +2419,17 @@ func TestSweepAnchors(t *testing.T) {
HtlcIndex: htlcIndexBase + 2,
RefundTimeout: htlcExpiryBase + 2,
RHash: rHash,
Amt: htlcAmt,
}
htlcSmallExipry := channeldb.HTLC{
HtlcIndex: htlcIndexBase + 3,
RefundTimeout: htlcExpiryBase + 3,
Amt: htlcAmt,
}
// Setup our local HTLC set such that we will use the HTLC's CLTV from
// the incoming HTLC set.
expectedLocalDeadline := htlcWithPreimage.RefundTimeout - heightHint
expectedLocalDeadline := htlcWithPreimage.RefundTimeout
chanArb.activeHTLCs[LocalHtlcSet] = htlcSet{
incomingHTLCs: map[uint64]channeldb.HTLC{
htlcWithPreimage.HtlcIndex: htlcWithPreimage,
@ -2442,8 +2448,7 @@ func TestSweepAnchors(t *testing.T) {
}
// Setup our remote HTLC set such that no valid HTLCs can be used, thus
// we default to anchorSweepConfTarget.
expectedRemoteDeadline := anchorSweepConfTarget
// the anchor sweeping is skipped.
chanArb.activeHTLCs[RemoteHtlcSet] = htlcSet{
incomingHTLCs: map[uint64]channeldb.HTLC{
htlcSmallExipry.HtlcIndex: htlcSmallExipry,
@ -2463,7 +2468,7 @@ func TestSweepAnchors(t *testing.T) {
// Setup out pending remote HTLC set such that we will use the HTLC's
// CLTV from the outgoing HTLC set.
expectedPendingDeadline := htlcSmallExipry.RefundTimeout - heightHint
expectedPendingDeadline := htlcSmallExipry.RefundTimeout
chanArb.activeHTLCs[RemotePendingHtlcSet] = htlcSet{
incomingHTLCs: map[uint64]channeldb.HTLC{
htlcDust.HtlcIndex: htlcDust,
@ -2506,20 +2511,22 @@ func TestSweepAnchors(t *testing.T) {
// Verify deadlines are used as expected.
deadlines := chanArbCtx.sweeper.deadlines
// We should see two `SweepInput` calls.
require.Len(t, deadlines, 2)
// Since there's no guarantee of the deadline orders, we sort it here
// so they can be compared.
sort.Ints(deadlines) // [12, 13, 144]
sort.Ints(deadlines) // [12, 13]
require.EqualValues(
t, expectedLocalDeadline, deadlines[0],
"local deadline not matched",
"local deadline not matched, want %v, got %v",
expectedLocalDeadline, deadlines[0],
)
require.EqualValues(
t, expectedPendingDeadline, deadlines[1],
"pending remote deadline not matched",
)
require.EqualValues(
t, expectedRemoteDeadline, deadlines[2],
"remote deadline not matched",
"pending remote deadline not matched, want %v, got %v",
expectedPendingDeadline, deadlines[1],
)
}
@ -2582,6 +2589,8 @@ func TestChannelArbitratorAnchors(t *testing.T) {
heightHint := uint32(1000)
chanArbCtx.chanArb.blocks <- int32(heightHint)
htlcAmt := lnwire.MilliSatoshi(1_000_000)
// Create testing HTLCs.
htlcExpiryBase := heightHint + uint32(10)
htlcWithPreimage := channeldb.HTLC{
@ -2589,10 +2598,12 @@ func TestChannelArbitratorAnchors(t *testing.T) {
RefundTimeout: htlcExpiryBase + 2,
RHash: rHash,
Incoming: true,
Amt: htlcAmt,
}
htlc := channeldb.HTLC{
HtlcIndex: 100,
RefundTimeout: htlcExpiryBase + 3,
Amt: htlcAmt,
}
// We now send two HTLC updates, one for local HTLC set and the other
@ -2600,9 +2611,9 @@ func TestChannelArbitratorAnchors(t *testing.T) {
newUpdate := &ContractUpdate{
HtlcKey: LocalHtlcSet,
// This will make the deadline of the local anchor resolution
// to be htlcWithPreimage's CLTV minus heightHint since the
// incoming HTLC (toLocalHTLCs) has a lower CLTV value and is
// preimage available.
// to be htlcWithPreimage's CLTV since the incoming HTLC
// (toLocalHTLCs) has a lower CLTV value and is preimage
// available.
Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage},
}
chanArb.notifyContractUpdate(newUpdate)
@ -2610,8 +2621,8 @@ func TestChannelArbitratorAnchors(t *testing.T) {
newUpdate = &ContractUpdate{
HtlcKey: RemoteHtlcSet,
// This will make the deadline of the remote anchor resolution
// to be htlcWithPreimage's CLTV minus heightHint because the
// incoming HTLC (toRemoteHTLCs) has a lower CLTV.
// to be htlcWithPreimage's CLTV because the incoming HTLC
// (toRemoteHTLCs) has a lower CLTV.
Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage},
}
chanArb.notifyContractUpdate(newUpdate)
@ -2734,14 +2745,14 @@ func TestChannelArbitratorAnchors(t *testing.T) {
// We expect two anchor inputs, the local and the remote to be swept.
// Thus we should expect there are two deadlines used, both are equal
// to htlcWithPreimage's CLTV minus current block height.
// to htlcWithPreimage's CLTV.
require.Equal(t, 2, len(chanArbCtx.sweeper.deadlines))
require.EqualValues(t,
htlcWithPreimage.RefundTimeout-heightHint,
htlcWithPreimage.RefundTimeout,
chanArbCtx.sweeper.deadlines[0],
)
require.EqualValues(t,
htlcWithPreimage.RefundTimeout-heightHint,
htlcWithPreimage.RefundTimeout,
chanArbCtx.sweeper.deadlines[1],
)
}

View File

@ -143,6 +143,11 @@ func (s *mockSweeper) SweepInput(input input.Input, params sweep.Params) (
}
}
// Update the deadlines used if it's set.
params.DeadlineHeight.WhenSome(func(d int32) {
s.deadlines = append(s.deadlines, int(d))
})
result := make(chan sweep.Result, 1)
result <- sweep.Result{
Tx: s.sweepTx,