watchtower/wtclient: add anchor backup tasks
This commit is contained in:
parent
97cb030854
commit
eb00e496bf
@ -39,6 +39,7 @@ import (
|
||||
type backupTask struct {
|
||||
id wtdb.BackupID
|
||||
breachInfo *lnwallet.BreachRetribution
|
||||
chanType channeldb.ChannelType
|
||||
|
||||
// state-dependent variables
|
||||
|
||||
@ -90,18 +91,33 @@ func newBackupTask(chanID *lnwire.ChannelID,
|
||||
if breachInfo.LocalOutputSignDesc != nil {
|
||||
var witnessType input.WitnessType
|
||||
switch {
|
||||
case chanType.HasAnchors():
|
||||
witnessType = input.CommitmentToRemoteConfirmed
|
||||
case chanType.IsTweakless():
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
default:
|
||||
witnessType = input.CommitmentNoDelay
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
// Anchor channels have a CSV-encumbered to-remote output. We'll
|
||||
// construct a CSV input in that case and assign the proper CSV
|
||||
// delay of 1, otherwise we fallback to the a regular P2WKH
|
||||
// to-remote output for tweaked or tweakless channels.
|
||||
if chanType.HasAnchors() {
|
||||
toRemoteInput = input.NewCsvInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0, 1,
|
||||
)
|
||||
} else {
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
|
||||
}
|
||||
@ -112,6 +128,7 @@ func newBackupTask(chanID *lnwire.ChannelID,
|
||||
CommitHeight: breachInfo.RevokedStateNum,
|
||||
},
|
||||
breachInfo: breachInfo,
|
||||
chanType: chanType,
|
||||
toLocalInput: toLocalInput,
|
||||
toRemoteInput: toRemoteInput,
|
||||
totalAmt: btcutil.Amount(totalAmt),
|
||||
@ -151,13 +168,28 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error {
|
||||
// underestimate the size by one byte. The diferrence in weight
|
||||
// can cause different output values on the sweep transaction,
|
||||
// so we mimic the original bug and create signatures using the
|
||||
// original weight estimate.
|
||||
weightEstimate.AddWitnessInput(
|
||||
input.ToLocalPenaltyWitnessSize - 1,
|
||||
)
|
||||
// original weight estimate. For anchor channels we'll go ahead
|
||||
// an use the correct penalty witness when signing our justice
|
||||
// transactions.
|
||||
if t.chanType.HasAnchors() {
|
||||
weightEstimate.AddWitnessInput(
|
||||
input.ToLocalPenaltyWitnessSize,
|
||||
)
|
||||
} else {
|
||||
weightEstimate.AddWitnessInput(
|
||||
input.ToLocalPenaltyWitnessSize - 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
if t.toRemoteInput != nil {
|
||||
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
||||
// Legacy channels (both tweaked and non-tweaked) spend from
|
||||
// P2WKH output. Anchor channels spend a to-remote confirmed
|
||||
// P2WSH output.
|
||||
if t.chanType.HasAnchors() {
|
||||
weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize)
|
||||
} else {
|
||||
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
||||
}
|
||||
}
|
||||
|
||||
// All justice transactions have a p2wkh output paying to the victim.
|
||||
@ -169,6 +201,12 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error {
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
}
|
||||
|
||||
if t.chanType.HasAnchors() != session.Policy.IsAnchorChannel() {
|
||||
log.Criticalf("Invalid task (has_anchors=%t) for session "+
|
||||
"(has_anchors=%t)", t.chanType.HasAnchors(),
|
||||
session.Policy.IsAnchorChannel())
|
||||
}
|
||||
|
||||
// Now, compute the output values depending on whether FlagReward is set
|
||||
// in the current session's policy.
|
||||
outputs, err := session.Policy.ComputeJusticeTxOuts(
|
||||
@ -225,9 +263,10 @@ func (t *backupTask) craftSessionPayload(
|
||||
// information. This will either be contain both the to-local and
|
||||
// to-remote outputs, or only be the to-local output.
|
||||
inputs := t.inputs()
|
||||
for prevOutPoint := range inputs {
|
||||
for prevOutPoint, input := range inputs {
|
||||
justiceTxn.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: prevOutPoint,
|
||||
Sequence: input.BlocksToMaturity(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -294,6 +333,8 @@ func (t *backupTask) craftSessionPayload(
|
||||
case input.CommitSpendNoDelayTweakless:
|
||||
fallthrough
|
||||
case input.CommitmentNoDelay:
|
||||
fallthrough
|
||||
case input.CommitmentToRemoteConfirmed:
|
||||
copy(justiceKit.CommitToRemoteSig[:], signature[:])
|
||||
default:
|
||||
return hint, nil, fmt.Errorf("invalid witness type: %v",
|
||||
|
@ -95,6 +95,12 @@ func genTaskTest(
|
||||
bindErr error,
|
||||
chanType channeldb.ChannelType) backupTaskTest {
|
||||
|
||||
// Set the anchor flag in the blob type if the session needs to support
|
||||
// anchor channels.
|
||||
if chanType.HasAnchors() {
|
||||
blobType |= blob.Type(blob.FlagAnchorChannel)
|
||||
}
|
||||
|
||||
// Parse the key pairs for all keys used in the test.
|
||||
revSK, revPK := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), revPrivBytes,
|
||||
@ -195,18 +201,29 @@ func genTaskTest(
|
||||
|
||||
var witnessType input.WitnessType
|
||||
switch {
|
||||
case chanType.HasAnchors():
|
||||
witnessType = input.CommitmentToRemoteConfirmed
|
||||
case chanType.IsTweakless():
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
default:
|
||||
witnessType = input.CommitmentNoDelay
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
if chanType.HasAnchors() {
|
||||
toRemoteInput = input.NewCsvInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0, 1,
|
||||
)
|
||||
} else {
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return backupTaskTest{
|
||||
@ -260,61 +277,93 @@ func TestBackupTask(t *testing.T) {
|
||||
chanTypes := []channeldb.ChannelType{
|
||||
channeldb.SingleFunderBit,
|
||||
channeldb.SingleFunderTweaklessBit,
|
||||
channeldb.AnchorOutputsBit,
|
||||
}
|
||||
|
||||
var backupTaskTests []backupTaskTest
|
||||
for _, chanType := range chanTypes {
|
||||
// Depending on whether the test is for anchor channels or
|
||||
// legacy (tweaked and non-tweaked) channels, adjust the
|
||||
// expected sweep amount to accommodate. These are different for
|
||||
// several reasons:
|
||||
// - anchor to-remote outputs require a P2WSH sweep rather
|
||||
// than a P2WKH sweep.
|
||||
// - the to-local weight estimate fixes an off-by-one.
|
||||
// In tests related to the dust threshold, the size difference
|
||||
// between the channel types makes it so that the threshold fee
|
||||
// rate is slightly lower (since the transactions are heavier).
|
||||
var (
|
||||
expSweepCommitNoRewardBoth int64 = 299241
|
||||
expSweepCommitNoRewardLocal int64 = 199514
|
||||
expSweepCommitNoRewardRemote int64 = 99561
|
||||
expSweepCommitRewardBoth int64 = 296117
|
||||
expSweepCommitRewardLocal int64 = 197390
|
||||
expSweepCommitRewardRemote int64 = 98437
|
||||
sweepFeeRateNoRewardRemoteDust chainfee.SatPerKWeight = 227500
|
||||
sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175000
|
||||
)
|
||||
if chanType.HasAnchors() {
|
||||
expSweepCommitNoRewardBoth = 299236
|
||||
expSweepCommitNoRewardLocal = 199513
|
||||
expSweepCommitNoRewardRemote = 99557
|
||||
expSweepCommitRewardBoth = 296112
|
||||
expSweepCommitRewardLocal = 197389
|
||||
expSweepCommitRewardRemote = 98433
|
||||
sweepFeeRateNoRewardRemoteDust = 225000
|
||||
sweepFeeRateRewardRemoteDust = 173750
|
||||
}
|
||||
|
||||
backupTaskTests = append(backupTaskTests, []backupTaskTest{
|
||||
genTaskTest(
|
||||
"commit no-reward, both outputs",
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
299241, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
expSweepCommitNoRewardBoth, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-local output only",
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
199514, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
expSweepCommitNoRewardLocal, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-remote output only",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
99561, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
expSweepCommitNoRewardRemote, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-remote output only, creates dust",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
227500, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
sweepFeeRateNoRewardRemoteDust, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
@ -345,54 +394,54 @@ func TestBackupTask(t *testing.T) {
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, both outputs",
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
296117, // expSweepAmt
|
||||
3000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
expSweepCommitRewardBoth, // expSweepAmt
|
||||
3000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-local output only",
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
197390, // expSweepAmt
|
||||
2000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
expSweepCommitRewardLocal, // expSweepAmt
|
||||
2000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-remote output only",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
98437, // expSweepAmt
|
||||
1000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
expSweepCommitRewardRemote, // expSweepAmt
|
||||
1000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-remote output only, creates dust",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
175000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
sweepFeeRateRewardRemoteDust, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
|
Loading…
Reference in New Issue
Block a user