watchtower/wtclient: add anchor backup tasks

This commit is contained in:
Conner Fromknecht 2020-11-25 15:05:17 -08:00
parent 97cb030854
commit eb00e496bf
No known key found for this signature in database
GPG Key ID: E7D737B67FA592C7
2 changed files with 180 additions and 90 deletions

@ -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(