contractcourt: specify deadline and budget for anchor output
This commit is contained in:
4 files changed
+91
-51
No files matched your search
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/sweep"
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
)
|
)
|
||||||
@@ -118,6 +119,14 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
|
|||||||
Fee: sweep.FeeEstimateInfo{
|
Fee: sweep.FeeEstimateInfo{
|
||||||
FeeRate: relayFeeRate,
|
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 {
|
if err != nil {
|
||||||
|
|||||||
@@ -37,12 +37,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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
|
// arbitratorBlockBufferSize is the size of the buffer we give to each
|
||||||
// channel arbitrator.
|
// channel arbitrator.
|
||||||
arbitratorBlockBufferSize = 20
|
arbitratorBlockBufferSize = 20
|
||||||
@@ -1321,36 +1315,23 @@ 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.
|
||||||
deadlineOpt, _, err := c.findCommitmentDeadlineAndValue(
|
deadline, value, err := c.findCommitmentDeadlineAndValue(
|
||||||
heightHint, htlcs,
|
heightHint, htlcs,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline := uint32(1)
|
// If we cannot find a deadline, it means there's no HTLCs at
|
||||||
deadlineOpt.WhenSome(func(d int32) {
|
// stake, which means we can relax our anchor sweeping as we
|
||||||
deadline = uint32(d)
|
// 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
|
return nil
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
witnessType := input.CommitmentAnchor
|
||||||
|
|
||||||
// For taproot channels, we need to use the proper witness
|
// 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
|
// Sweep anchor output with a confirmation target fee
|
||||||
// preference. Because this is a cpfp-operation, the anchor
|
// preference. Because this is a cpfp-operation, the anchor
|
||||||
// will only be attempted to sweep when the current fee
|
// 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(
|
_, err = c.cfg.Sweeper.SweepInput(
|
||||||
&anchorInput,
|
&anchorInput,
|
||||||
sweep.Params{
|
sweep.Params{
|
||||||
Fee: sweep.FeeEstimateInfo{
|
|
||||||
ConfTarget: deadline,
|
|
||||||
},
|
|
||||||
Force: force,
|
|
||||||
ExclusiveGroup: &exclusiveGroup,
|
ExclusiveGroup: &exclusiveGroup,
|
||||||
|
Budget: budget,
|
||||||
|
DeadlineHeight: deadlineHeight,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2138,6 +2139,20 @@ func (c *ChannelArbitrator) checkRemoteDiffActions(height uint32,
|
|||||||
continue
|
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] = append(
|
||||||
actionMap[HtlcFailNowAction], htlc,
|
actionMap[HtlcFailNowAction], htlc,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -391,7 +391,9 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Budget: *DefaultBudgetConfig(),
|
Budget: *DefaultBudgetConfig(),
|
||||||
|
PreimageDB: newMockWitnessBeacon(),
|
||||||
|
Registry: &mockRegistry{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll use the resolvedChan to synchronize on call to
|
// We'll use the resolvedChan to synchronize on call to
|
||||||
@@ -2405,6 +2407,8 @@ func TestSweepAnchors(t *testing.T) {
|
|||||||
htlcIndexBase := uint64(99)
|
htlcIndexBase := uint64(99)
|
||||||
htlcExpiryBase := heightHint + uint32(10)
|
htlcExpiryBase := heightHint + uint32(10)
|
||||||
|
|
||||||
|
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,
|
||||||
@@ -2415,15 +2419,17 @@ func TestSweepAnchors(t *testing.T) {
|
|||||||
HtlcIndex: htlcIndexBase + 2,
|
HtlcIndex: htlcIndexBase + 2,
|
||||||
RefundTimeout: htlcExpiryBase + 2,
|
RefundTimeout: htlcExpiryBase + 2,
|
||||||
RHash: rHash,
|
RHash: rHash,
|
||||||
|
Amt: htlcAmt,
|
||||||
}
|
}
|
||||||
htlcSmallExipry := channeldb.HTLC{
|
htlcSmallExipry := channeldb.HTLC{
|
||||||
HtlcIndex: htlcIndexBase + 3,
|
HtlcIndex: htlcIndexBase + 3,
|
||||||
RefundTimeout: htlcExpiryBase + 3,
|
RefundTimeout: htlcExpiryBase + 3,
|
||||||
|
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 - heightHint
|
expectedLocalDeadline := htlcWithPreimage.RefundTimeout
|
||||||
chanArb.activeHTLCs[LocalHtlcSet] = htlcSet{
|
chanArb.activeHTLCs[LocalHtlcSet] = htlcSet{
|
||||||
incomingHTLCs: map[uint64]channeldb.HTLC{
|
incomingHTLCs: map[uint64]channeldb.HTLC{
|
||||||
htlcWithPreimage.HtlcIndex: htlcWithPreimage,
|
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
|
// Setup our remote HTLC set such that no valid HTLCs can be used, thus
|
||||||
// we default to anchorSweepConfTarget.
|
// the anchor sweeping is skipped.
|
||||||
expectedRemoteDeadline := anchorSweepConfTarget
|
|
||||||
chanArb.activeHTLCs[RemoteHtlcSet] = htlcSet{
|
chanArb.activeHTLCs[RemoteHtlcSet] = htlcSet{
|
||||||
incomingHTLCs: map[uint64]channeldb.HTLC{
|
incomingHTLCs: map[uint64]channeldb.HTLC{
|
||||||
htlcSmallExipry.HtlcIndex: htlcSmallExipry,
|
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
|
// 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 - heightHint
|
expectedPendingDeadline := htlcSmallExipry.RefundTimeout
|
||||||
chanArb.activeHTLCs[RemotePendingHtlcSet] = htlcSet{
|
chanArb.activeHTLCs[RemotePendingHtlcSet] = htlcSet{
|
||||||
incomingHTLCs: map[uint64]channeldb.HTLC{
|
incomingHTLCs: map[uint64]channeldb.HTLC{
|
||||||
htlcDust.HtlcIndex: htlcDust,
|
htlcDust.HtlcIndex: htlcDust,
|
||||||
@@ -2506,20 +2511,22 @@ func TestSweepAnchors(t *testing.T) {
|
|||||||
|
|
||||||
// Verify deadlines are used as expected.
|
// Verify deadlines are used as expected.
|
||||||
deadlines := chanArbCtx.sweeper.deadlines
|
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
|
// Since there's no guarantee of the deadline orders, we sort it here
|
||||||
// so they can be compared.
|
// so they can be compared.
|
||||||
sort.Ints(deadlines) // [12, 13, 144]
|
sort.Ints(deadlines) // [12, 13]
|
||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
t, expectedLocalDeadline, deadlines[0],
|
t, expectedLocalDeadline, deadlines[0],
|
||||||
"local deadline not matched",
|
"local deadline not matched, want %v, got %v",
|
||||||
|
expectedLocalDeadline, deadlines[0],
|
||||||
)
|
)
|
||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
t, expectedPendingDeadline, deadlines[1],
|
t, expectedPendingDeadline, deadlines[1],
|
||||||
"pending remote deadline not matched",
|
"pending remote deadline not matched, want %v, got %v",
|
||||||
)
|
expectedPendingDeadline, deadlines[1],
|
||||||
require.EqualValues(
|
|
||||||
t, expectedRemoteDeadline, deadlines[2],
|
|
||||||
"remote deadline not matched",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2582,6 +2589,8 @@ func TestChannelArbitratorAnchors(t *testing.T) {
|
|||||||
heightHint := uint32(1000)
|
heightHint := uint32(1000)
|
||||||
chanArbCtx.chanArb.blocks <- int32(heightHint)
|
chanArbCtx.chanArb.blocks <- int32(heightHint)
|
||||||
|
|
||||||
|
htlcAmt := lnwire.MilliSatoshi(1_000_000)
|
||||||
|
|
||||||
// Create testing HTLCs.
|
// Create testing HTLCs.
|
||||||
htlcExpiryBase := heightHint + uint32(10)
|
htlcExpiryBase := heightHint + uint32(10)
|
||||||
htlcWithPreimage := channeldb.HTLC{
|
htlcWithPreimage := channeldb.HTLC{
|
||||||
@@ -2589,10 +2598,12 @@ func TestChannelArbitratorAnchors(t *testing.T) {
|
|||||||
RefundTimeout: htlcExpiryBase + 2,
|
RefundTimeout: htlcExpiryBase + 2,
|
||||||
RHash: rHash,
|
RHash: rHash,
|
||||||
Incoming: true,
|
Incoming: true,
|
||||||
|
Amt: htlcAmt,
|
||||||
}
|
}
|
||||||
htlc := channeldb.HTLC{
|
htlc := channeldb.HTLC{
|
||||||
HtlcIndex: 100,
|
HtlcIndex: 100,
|
||||||
RefundTimeout: htlcExpiryBase + 3,
|
RefundTimeout: htlcExpiryBase + 3,
|
||||||
|
Amt: htlcAmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We now send two HTLC updates, one for local HTLC set and the other
|
// 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{
|
newUpdate := &ContractUpdate{
|
||||||
HtlcKey: LocalHtlcSet,
|
HtlcKey: LocalHtlcSet,
|
||||||
// This will make the deadline of the local anchor resolution
|
// This will make the deadline of the local anchor resolution
|
||||||
// to be htlcWithPreimage's CLTV minus heightHint since the
|
// to be htlcWithPreimage's CLTV since the incoming HTLC
|
||||||
// incoming HTLC (toLocalHTLCs) has a lower CLTV value and is
|
// (toLocalHTLCs) has a lower CLTV value and is preimage
|
||||||
// preimage available.
|
// available.
|
||||||
Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage},
|
Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage},
|
||||||
}
|
}
|
||||||
chanArb.notifyContractUpdate(newUpdate)
|
chanArb.notifyContractUpdate(newUpdate)
|
||||||
@@ -2610,8 +2621,8 @@ func TestChannelArbitratorAnchors(t *testing.T) {
|
|||||||
newUpdate = &ContractUpdate{
|
newUpdate = &ContractUpdate{
|
||||||
HtlcKey: RemoteHtlcSet,
|
HtlcKey: RemoteHtlcSet,
|
||||||
// This will make the deadline of the remote anchor resolution
|
// This will make the deadline of the remote anchor resolution
|
||||||
// to be htlcWithPreimage's CLTV minus heightHint because the
|
// to be htlcWithPreimage's CLTV because the incoming HTLC
|
||||||
// incoming HTLC (toRemoteHTLCs) has a lower CLTV.
|
// (toRemoteHTLCs) has a lower CLTV.
|
||||||
Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage},
|
Htlcs: []channeldb.HTLC{htlc, htlcWithPreimage},
|
||||||
}
|
}
|
||||||
chanArb.notifyContractUpdate(newUpdate)
|
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.
|
// 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
|
// 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.Equal(t, 2, len(chanArbCtx.sweeper.deadlines))
|
||||||
require.EqualValues(t,
|
require.EqualValues(t,
|
||||||
htlcWithPreimage.RefundTimeout-heightHint,
|
htlcWithPreimage.RefundTimeout,
|
||||||
chanArbCtx.sweeper.deadlines[0],
|
chanArbCtx.sweeper.deadlines[0],
|
||||||
)
|
)
|
||||||
require.EqualValues(t,
|
require.EqualValues(t,
|
||||||
htlcWithPreimage.RefundTimeout-heightHint,
|
htlcWithPreimage.RefundTimeout,
|
||||||
chanArbCtx.sweeper.deadlines[1],
|
chanArbCtx.sweeper.deadlines[1],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 := make(chan sweep.Result, 1)
|
||||||
result <- sweep.Result{
|
result <- sweep.Result{
|
||||||
Tx: s.sweepTx,
|
Tx: s.sweepTx,
|
||||||
|
|||||||
Reference in new issue
Block a user