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 { type backupTask struct {
id wtdb.BackupID id wtdb.BackupID
breachInfo *lnwallet.BreachRetribution breachInfo *lnwallet.BreachRetribution
chanType channeldb.ChannelType
// state-dependent variables // state-dependent variables
@ -90,18 +91,33 @@ func newBackupTask(chanID *lnwire.ChannelID,
if breachInfo.LocalOutputSignDesc != nil { if breachInfo.LocalOutputSignDesc != nil {
var witnessType input.WitnessType var witnessType input.WitnessType
switch { switch {
case chanType.HasAnchors():
witnessType = input.CommitmentToRemoteConfirmed
case chanType.IsTweakless(): case chanType.IsTweakless():
witnessType = input.CommitSpendNoDelayTweakless witnessType = input.CommitSpendNoDelayTweakless
default: default:
witnessType = input.CommitmentNoDelay witnessType = input.CommitmentNoDelay
} }
// 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( toRemoteInput = input.NewBaseInput(
&breachInfo.LocalOutpoint, &breachInfo.LocalOutpoint,
witnessType, witnessType,
breachInfo.LocalOutputSignDesc, breachInfo.LocalOutputSignDesc,
0, 0,
) )
}
totalAmt += breachInfo.LocalOutputSignDesc.Output.Value totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
} }
@ -112,6 +128,7 @@ func newBackupTask(chanID *lnwire.ChannelID,
CommitHeight: breachInfo.RevokedStateNum, CommitHeight: breachInfo.RevokedStateNum,
}, },
breachInfo: breachInfo, breachInfo: breachInfo,
chanType: chanType,
toLocalInput: toLocalInput, toLocalInput: toLocalInput,
toRemoteInput: toRemoteInput, toRemoteInput: toRemoteInput,
totalAmt: btcutil.Amount(totalAmt), totalAmt: btcutil.Amount(totalAmt),
@ -151,14 +168,29 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error {
// underestimate the size by one byte. The diferrence in weight // underestimate the size by one byte. The diferrence in weight
// can cause different output values on the sweep transaction, // can cause different output values on the sweep transaction,
// so we mimic the original bug and create signatures using the // so we mimic the original bug and create signatures using the
// original weight estimate. // 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( weightEstimate.AddWitnessInput(
input.ToLocalPenaltyWitnessSize - 1, input.ToLocalPenaltyWitnessSize - 1,
) )
} }
}
if t.toRemoteInput != nil { if t.toRemoteInput != nil {
// 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) weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
} }
}
// All justice transactions have a p2wkh output paying to the victim. // All justice transactions have a p2wkh output paying to the victim.
weightEstimate.AddP2WKHOutput() weightEstimate.AddP2WKHOutput()
@ -169,6 +201,12 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error {
weightEstimate.AddP2WKHOutput() 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 // Now, compute the output values depending on whether FlagReward is set
// in the current session's policy. // in the current session's policy.
outputs, err := session.Policy.ComputeJusticeTxOuts( outputs, err := session.Policy.ComputeJusticeTxOuts(
@ -225,9 +263,10 @@ func (t *backupTask) craftSessionPayload(
// information. This will either be contain both the to-local and // information. This will either be contain both the to-local and
// to-remote outputs, or only be the to-local output. // to-remote outputs, or only be the to-local output.
inputs := t.inputs() inputs := t.inputs()
for prevOutPoint := range inputs { for prevOutPoint, input := range inputs {
justiceTxn.AddTxIn(&wire.TxIn{ justiceTxn.AddTxIn(&wire.TxIn{
PreviousOutPoint: prevOutPoint, PreviousOutPoint: prevOutPoint,
Sequence: input.BlocksToMaturity(),
}) })
} }
@ -294,6 +333,8 @@ func (t *backupTask) craftSessionPayload(
case input.CommitSpendNoDelayTweakless: case input.CommitSpendNoDelayTweakless:
fallthrough fallthrough
case input.CommitmentNoDelay: case input.CommitmentNoDelay:
fallthrough
case input.CommitmentToRemoteConfirmed:
copy(justiceKit.CommitToRemoteSig[:], signature[:]) copy(justiceKit.CommitToRemoteSig[:], signature[:])
default: default:
return hint, nil, fmt.Errorf("invalid witness type: %v", return hint, nil, fmt.Errorf("invalid witness type: %v",

@ -95,6 +95,12 @@ func genTaskTest(
bindErr error, bindErr error,
chanType channeldb.ChannelType) backupTaskTest { 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. // Parse the key pairs for all keys used in the test.
revSK, revPK := btcec.PrivKeyFromBytes( revSK, revPK := btcec.PrivKeyFromBytes(
btcec.S256(), revPrivBytes, btcec.S256(), revPrivBytes,
@ -195,12 +201,22 @@ func genTaskTest(
var witnessType input.WitnessType var witnessType input.WitnessType
switch { switch {
case chanType.HasAnchors():
witnessType = input.CommitmentToRemoteConfirmed
case chanType.IsTweakless(): case chanType.IsTweakless():
witnessType = input.CommitSpendNoDelayTweakless witnessType = input.CommitSpendNoDelayTweakless
default: default:
witnessType = input.CommitmentNoDelay witnessType = input.CommitmentNoDelay
} }
if chanType.HasAnchors() {
toRemoteInput = input.NewCsvInput(
&breachInfo.LocalOutpoint,
witnessType,
breachInfo.LocalOutputSignDesc,
0, 1,
)
} else {
toRemoteInput = input.NewBaseInput( toRemoteInput = input.NewBaseInput(
&breachInfo.LocalOutpoint, &breachInfo.LocalOutpoint,
witnessType, witnessType,
@ -208,6 +224,7 @@ func genTaskTest(
0, 0,
) )
} }
}
return backupTaskTest{ return backupTaskTest{
name: name, name: name,
@ -260,10 +277,42 @@ func TestBackupTask(t *testing.T) {
chanTypes := []channeldb.ChannelType{ chanTypes := []channeldb.ChannelType{
channeldb.SingleFunderBit, channeldb.SingleFunderBit,
channeldb.SingleFunderTweaklessBit, channeldb.SingleFunderTweaklessBit,
channeldb.AnchorOutputsBit,
} }
var backupTaskTests []backupTaskTest var backupTaskTests []backupTaskTest
for _, chanType := range chanTypes { 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{ backupTaskTests = append(backupTaskTests, []backupTaskTest{
genTaskTest( genTaskTest(
"commit no-reward, both outputs", "commit no-reward, both outputs",
@ -273,7 +322,7 @@ func TestBackupTask(t *testing.T) {
blobTypeCommitNoReward, // blobType blobTypeCommitNoReward, // blobType
1000, // sweepFeeRate 1000, // sweepFeeRate
nil, // rewardScript nil, // rewardScript
299241, // expSweepAmt expSweepCommitNoRewardBoth, // expSweepAmt
0, // expRewardAmt 0, // expRewardAmt
nil, // bindErr nil, // bindErr
chanType, chanType,
@ -286,7 +335,7 @@ func TestBackupTask(t *testing.T) {
blobTypeCommitNoReward, // blobType blobTypeCommitNoReward, // blobType
1000, // sweepFeeRate 1000, // sweepFeeRate
nil, // rewardScript nil, // rewardScript
199514, // expSweepAmt expSweepCommitNoRewardLocal, // expSweepAmt
0, // expRewardAmt 0, // expRewardAmt
nil, // bindErr nil, // bindErr
chanType, chanType,
@ -299,7 +348,7 @@ func TestBackupTask(t *testing.T) {
blobTypeCommitNoReward, // blobType blobTypeCommitNoReward, // blobType
1000, // sweepFeeRate 1000, // sweepFeeRate
nil, // rewardScript nil, // rewardScript
99561, // expSweepAmt expSweepCommitNoRewardRemote, // expSweepAmt
0, // expRewardAmt 0, // expRewardAmt
nil, // bindErr nil, // bindErr
chanType, chanType,
@ -310,7 +359,7 @@ func TestBackupTask(t *testing.T) {
0, // toLocalAmt 0, // toLocalAmt
100000, // toRemoteAmt 100000, // toRemoteAmt
blobTypeCommitNoReward, // blobType blobTypeCommitNoReward, // blobType
227500, // sweepFeeRate sweepFeeRateNoRewardRemoteDust, // sweepFeeRate
nil, // rewardScript nil, // rewardScript
0, // expSweepAmt 0, // expSweepAmt
0, // expRewardAmt 0, // expRewardAmt
@ -351,7 +400,7 @@ func TestBackupTask(t *testing.T) {
blobTypeCommitReward, // blobType blobTypeCommitReward, // blobType
1000, // sweepFeeRate 1000, // sweepFeeRate
addrScript, // rewardScript addrScript, // rewardScript
296117, // expSweepAmt expSweepCommitRewardBoth, // expSweepAmt
3000, // expRewardAmt 3000, // expRewardAmt
nil, // bindErr nil, // bindErr
chanType, chanType,
@ -364,7 +413,7 @@ func TestBackupTask(t *testing.T) {
blobTypeCommitReward, // blobType blobTypeCommitReward, // blobType
1000, // sweepFeeRate 1000, // sweepFeeRate
addrScript, // rewardScript addrScript, // rewardScript
197390, // expSweepAmt expSweepCommitRewardLocal, // expSweepAmt
2000, // expRewardAmt 2000, // expRewardAmt
nil, // bindErr nil, // bindErr
chanType, chanType,
@ -377,7 +426,7 @@ func TestBackupTask(t *testing.T) {
blobTypeCommitReward, // blobType blobTypeCommitReward, // blobType
1000, // sweepFeeRate 1000, // sweepFeeRate
addrScript, // rewardScript addrScript, // rewardScript
98437, // expSweepAmt expSweepCommitRewardRemote, // expSweepAmt
1000, // expRewardAmt 1000, // expRewardAmt
nil, // bindErr nil, // bindErr
chanType, chanType,
@ -388,7 +437,7 @@ func TestBackupTask(t *testing.T) {
0, // toLocalAmt 0, // toLocalAmt
100000, // toRemoteAmt 100000, // toRemoteAmt
blobTypeCommitReward, // blobType blobTypeCommitReward, // blobType
175000, // sweepFeeRate sweepFeeRateRewardRemoteDust, // sweepFeeRate
addrScript, // rewardScript addrScript, // rewardScript
0, // expSweepAmt 0, // expSweepAmt
0, // expRewardAmt 0, // expRewardAmt