diff --git a/watchtower/blob/commitments.go b/watchtower/blob/commitments.go new file mode 100644 index 000000000..4ae935045 --- /dev/null +++ b/watchtower/blob/commitments.go @@ -0,0 +1,174 @@ +package blob + +import ( + "fmt" + + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" +) + +// CommitmentType characterises the various properties of the breach commitment +// transaction. +type CommitmentType uint8 + +const ( + // LegacyCommitment represents a legacy commitment transaction where + // anchor outputs are not yet used and so the to_remote output is just + // a regular but tweaked P2WKH. + LegacyCommitment CommitmentType = iota + + // LegacyTweaklessCommitment is similar to the LegacyCommitment with the + // added detail of the to_remote output not being tweaked. + LegacyTweaklessCommitment + + // AnchorCommitment represents the commitment transaction of an + // anchor channel. The key differences are that the to_remote is + // encumbered by a 1 block CSV and so is thus a P2WSH output. + AnchorCommitment +) + +// ToLocalInput constructs the input that will be used to spend the to_local +// output. +func (c CommitmentType) ToLocalInput(info *lnwallet.BreachRetribution) ( + input.Input, error) { + + witnessType, err := c.ToLocalWitnessType() + if err != nil { + return nil, err + } + + return input.NewBaseInput( + &info.RemoteOutpoint, witnessType, info.RemoteOutputSignDesc, 0, + ), nil +} + +// ToRemoteInput constructs the input that will be used to spend the to_remote +// output. +func (c CommitmentType) ToRemoteInput(info *lnwallet.BreachRetribution) ( + input.Input, error) { + + witnessType, err := c.ToRemoteWitnessType() + if err != nil { + return nil, err + } + + switch c { + case LegacyCommitment, LegacyTweaklessCommitment: + return input.NewBaseInput( + &info.LocalOutpoint, witnessType, + info.LocalOutputSignDesc, 0, + ), nil + + case AnchorCommitment: + // Anchor channels have a CSV-encumbered to-remote output. We'll + // construct a CSV input and assign the proper CSV delay of 1. + return input.NewCsvInput( + &info.LocalOutpoint, witnessType, + info.LocalOutputSignDesc, 0, 1, + ), nil + + default: + return nil, fmt.Errorf("unknown commitment type: %v", c) + } +} + +// ToLocalWitnessType is the input type of the to_local output. +func (c CommitmentType) ToLocalWitnessType() (input.WitnessType, error) { + switch c { + case LegacyTweaklessCommitment, LegacyCommitment, AnchorCommitment: + return input.CommitmentRevoke, nil + + default: + return nil, fmt.Errorf("unknown commitment type: %v", c) + } +} + +// ToRemoteWitnessType is the input type of the to_remote output. +func (c CommitmentType) ToRemoteWitnessType() (input.WitnessType, error) { + switch c { + case LegacyTweaklessCommitment: + return input.CommitSpendNoDelayTweakless, nil + + case LegacyCommitment: + return input.CommitmentNoDelay, nil + + case AnchorCommitment: + return input.CommitmentToRemoteConfirmed, nil + + default: + return nil, fmt.Errorf("unknown commitment type: %v", c) + } +} + +// ToRemoteWitnessSize is the size of the witness that will be required to spend +// the to_remote output. +func (c CommitmentType) ToRemoteWitnessSize() (int, error) { + switch c { + // Legacy channels (both tweaked and non-tweaked) spend from P2WKH + // output. + case LegacyTweaklessCommitment, LegacyCommitment: + return input.P2WKHWitnessSize, nil + + // Anchor channels spend a to-remote confirmed P2WSH output. + case AnchorCommitment: + return input.ToRemoteConfirmedWitnessSize, nil + + default: + return 0, fmt.Errorf("unknown commitment type: %v", c) + } +} + +// ToLocalWitnessSize is the size of the witness that will be required to spend +// the to_local output. +func (c CommitmentType) ToLocalWitnessSize() (int, error) { + switch c { + // An older ToLocalPenaltyWitnessSize constant used to underestimate the + // size by one byte. The difference 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. + case LegacyTweaklessCommitment, LegacyCommitment: + return input.ToLocalPenaltyWitnessSize - 1, nil + + case AnchorCommitment: + return input.ToLocalPenaltyWitnessSize, nil + + default: + return 0, fmt.Errorf("unknown commitment type: %v", c) + } +} + +// ParseRawSig parses a wire.TxWitness and creates an lnwire.Sig. +func (c CommitmentType) ParseRawSig(witness wire.TxWitness) (lnwire.Sig, + error) { + + switch c { + case LegacyCommitment, LegacyTweaklessCommitment, AnchorCommitment: + // Check that the witness has at least one item. + if len(witness) < 1 { + return lnwire.Sig{}, fmt.Errorf("the witness should " + + "have at least one element") + } + + // Check that the first witness element is non-nil. This is to + // ensure that the witness length check below does not panic. + if witness[0] == nil { + return lnwire.Sig{}, fmt.Errorf("the first witness " + + "element should not be nil") + } + + // Parse the DER-encoded signature from the first position of + // the resulting witness. We trim an extra byte to remove the + // sighash flag. + rawSignature := witness[0][:len(witness[0])-1] + + // Re-encode the DER signature into a fixed-size 64 byte + // signature. + return lnwire.NewSigFromECDSARawSignature(rawSignature) + + default: + return lnwire.Sig{}, fmt.Errorf("unknown commitment type: %v", + c) + } +} diff --git a/watchtower/blob/type.go b/watchtower/blob/type.go index 87dd52aab..f77befb89 100644 --- a/watchtower/blob/type.go +++ b/watchtower/blob/type.go @@ -3,6 +3,8 @@ package blob import ( "fmt" "strings" + + "github.com/lightningnetwork/lnd/channeldb" ) // Flag represents a specify option that can be present in a Type. @@ -81,6 +83,27 @@ func (t Type) Identifier() (string, error) { } } +// CommitmentType returns the appropriate CommitmentType for the given blob Type +// and channel type. +func (t Type) CommitmentType(chanType *channeldb.ChannelType) (CommitmentType, + error) { + + switch { + case t.Has(FlagAnchorChannel): + return AnchorCommitment, nil + + case t.Has(FlagCommitOutputs): + if chanType != nil && chanType.IsTweakless() { + return LegacyTweaklessCommitment, nil + } + + return LegacyCommitment, nil + + default: + return 0, ErrUnknownBlobType + } +} + // Has returns true if the Type has the passed flag enabled. func (t Type) Has(flag Flag) bool { return Flag(t)&flag == flag diff --git a/watchtower/lookout/justice_descriptor.go b/watchtower/lookout/justice_descriptor.go index a474d5ccb..2ed63500d 100644 --- a/watchtower/lookout/justice_descriptor.go +++ b/watchtower/lookout/justice_descriptor.go @@ -264,6 +264,11 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { weightEstimate input.TxWeightEstimator ) + commitmentType, err := p.SessionInfo.Policy.BlobType.CommitmentType(nil) + if err != nil { + return nil, err + } + // Add the sweep address's contribution, depending on whether it is a // p2wkh or p2wsh output. switch len(p.JusticeKit.SweepAddress) { @@ -290,16 +295,13 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { return nil, err } - // An older ToLocalPenaltyWitnessSize constant used to 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 to - // avoid invalidating signatures by older clients. For anchor channels - // we correct this and use the correct witness size. - if p.JusticeKit.BlobType.IsAnchorChannel() { - weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize) - } else { - weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1) + // Get the weight for the to-local witness and add that to the + // estimator. + toLocalWitnessSize, err := commitmentType.ToLocalWitnessSize() + if err != nil { + return nil, err } + weightEstimate.AddWitnessInput(toLocalWitnessSize) sweepInputs = append(sweepInputs, toLocalInput) @@ -319,11 +321,14 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { log.Debugf("Found to remote witness output=%#v, stack=%v", toRemoteInput.txOut, toRemoteInput.witness) - if p.JusticeKit.BlobType.IsAnchorChannel() { - weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize) - } else { - weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) + // Get the weight for the to-remote witness and add that to the + // estimator. + toRemoteWitnessSize, err := commitmentType.ToRemoteWitnessSize() + if err != nil { + return nil, err } + + weightEstimate.AddWitnessInput(toRemoteWitnessSize) } // TODO(conner): sweep htlc outputs diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index c37ed2693..42f8af38a 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -92,15 +92,13 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { ) // Parse the key pairs for all keys used in the test. - revSK, revPK := btcec.PrivKeyFromBytes( - revPrivBytes, - ) - _, toLocalPK := btcec.PrivKeyFromBytes( - toLocalPrivBytes, - ) - toRemoteSK, toRemotePK := btcec.PrivKeyFromBytes( - toRemotePrivBytes, - ) + revSK, revPK := btcec.PrivKeyFromBytes(revPrivBytes) + _, toLocalPK := btcec.PrivKeyFromBytes(toLocalPrivBytes) + toRemoteSK, toRemotePK := btcec.PrivKeyFromBytes(toRemotePrivBytes) + + // Get the commitment type. + commitType, err := blobType.CommitmentType(nil) + require.NoError(t, err) // Create the signer, and add the revocation and to-remote privkeys. signer := wtmock.NewMockSigner() @@ -113,11 +111,11 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { toLocalScript, err := input.CommitScriptToSelf( csvDelay, toLocalPK, revPK, ) - require.Nil(t, err) + require.NoError(t, err) // Compute the to-local witness script hash. toLocalScriptHash, err := input.WitnessScriptHash(toLocalScript) - require.Nil(t, err) + require.NoError(t, err) // Compute the to-remote redeem script, witness script hash, and // sequence numbers. @@ -147,12 +145,12 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { toRemoteRedeemScript, err = input.CommitScriptToRemoteConfirmed( toRemotePK, ) - require.Nil(t, err) + require.NoError(t, err) toRemoteScriptHash, err = input.WitnessScriptHash( toRemoteRedeemScript, ) - require.Nil(t, err) + require.NoError(t, err) // As it should be. toRemoteSigningScript = toRemoteRedeemScript @@ -161,7 +159,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { toRemoteScriptHash, err = input.CommitScriptUnencumbered( toRemotePK, ) - require.Nil(t, err) + require.NoError(t, err) // NOTE: This is the _pkscript_. toRemoteSigningScript = toRemoteScriptHash @@ -188,26 +186,24 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // Compute the weight estimate for our justice transaction. var weightEstimate input.TxWeightEstimator - // An older ToLocalPenaltyWitnessSize constant used to 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. For anchor - // channels we fix this and use the correct witness size. - if isAnchorChannel { - weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize) - } else { - weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1) - } + // Add the local witness size to the weight estimator. + toLocalWitnessSize, err := commitType.ToLocalWitnessSize() + require.NoError(t, err) + weightEstimate.AddWitnessInput(toLocalWitnessSize) - if isAnchorChannel { - weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize) - } else { - weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) - } + // Add the remote witness size to the weight estimator. + toRemoteWitnessSize, err := commitType.ToRemoteWitnessSize() + require.NoError(t, err) + weightEstimate.AddWitnessInput(toRemoteWitnessSize) + + // Add the sweep output to the weight estimator. weightEstimate.AddP2WKHOutput() + + // Add the reward output to the weight estimator. if blobType.Has(blob.FlagReward) { weightEstimate.AddP2WKHOutput() } + txWeight := weightEstimate.Weight() // Create a session info so that simulate agreement of the sweep @@ -263,7 +259,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { totalAmount, int64(txWeight), justiceKit.SweepAddress, sessionInfo.RewardAddress, ) - require.Nil(t, err) + require.NoError(t, err) // Attach the txouts and BIP69 sort the resulting transaction. justiceTxn.TxOut = outputs diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index 62cc3d367..d811af4c3 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/wtdb" ) @@ -37,8 +36,9 @@ import ( // necessary components are stripped out and encrypted before being sent to // the tower in a StateUpdate. type backupTask struct { - id wtdb.BackupID - breachInfo *lnwallet.BreachRetribution + id wtdb.BackupID + breachInfo *lnwallet.BreachRetribution + commitmentType blob.CommitmentType // state-dependent variables @@ -127,6 +127,11 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody, return err } + commitType, err := session.Policy.BlobType.CommitmentType(&chanType) + if err != nil { + return err + } + // Parse the non-dust outputs from the breach transaction, // simultaneously computing the total amount contained in the inputs // present. We can't compute the exact output values at this time @@ -147,48 +152,23 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody, // to that output as local, though relative to their commitment, it is // paying to-the-remote party (which is us). if breachInfo.RemoteOutputSignDesc != nil { - toLocalInput = input.NewBaseInput( - &breachInfo.RemoteOutpoint, - input.CommitmentRevoke, - breachInfo.RemoteOutputSignDesc, - 0, - ) + toLocalInput, err = commitType.ToLocalInput(breachInfo) + if err != nil { + return err + } + totalAmt += breachInfo.RemoteOutputSignDesc.Output.Value } 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 - } - - // 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, - ) + toRemoteInput, err = commitType.ToRemoteInput(breachInfo) + if err != nil { + return err } totalAmt += breachInfo.LocalOutputSignDesc.Output.Value } + t.commitmentType = commitType t.breachInfo = breachInfo t.toLocalInput = toLocalInput t.toRemoteInput = toRemoteInput @@ -202,34 +182,20 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody, // Next, add the contribution from the inputs that are present on this // breach transaction. if t.toLocalInput != nil { - // An older ToLocalPenaltyWitnessSize constant used to - // 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. For anchor channels we'll go ahead - // an use the correct penalty witness when signing our justice - // transactions. - if chanType.HasAnchors() { - weightEstimate.AddWitnessInput( - input.ToLocalPenaltyWitnessSize, - ) - } else { - weightEstimate.AddWitnessInput( - input.ToLocalPenaltyWitnessSize - 1, - ) + toLocalWitnessSize, err := commitType.ToLocalWitnessSize() + if err != nil { + return err } + + weightEstimate.AddWitnessInput(toLocalWitnessSize) } 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 chanType.HasAnchors() { - weightEstimate.AddWitnessInput( - input.ToRemoteConfirmedWitnessSize, - ) - } else { - weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) + toRemoteWitnessSize, err := commitType.ToRemoteWitnessSize() + if err != nil { + return err } + + weightEstimate.AddWitnessInput(toRemoteWitnessSize) } // All justice transactions will either use segwit v0 (p2wkh + p2wsh) @@ -349,6 +315,7 @@ func (t *backupTask) craftSessionPayload( // Now, iterate through the list of inputs that were initially added to // the transaction and store the computed witness within the justice // kit. + commitType := t.commitmentType for _, inp := range inputs { // Lookup the input's new post-sort position. i := inputIndex[*inp.OutPoint()] @@ -361,17 +328,17 @@ func (t *backupTask) craftSessionPayload( return hint, nil, err } - // Parse the DER-encoded signature from the first position of - // the resulting witness. We trim an extra byte to remove the - // sighash flag. - witness := inputScript.Witness - rawSignature := witness[0][:len(witness[0])-1] + signature, err := commitType.ParseRawSig(inputScript.Witness) + if err != nil { + return hint, nil, err + } - // Re-encode the DER signature into a fixed-size 64 byte - // signature. - signature, err := lnwire.NewSigFromECDSARawSignature( - rawSignature, - ) + toLocalWitnessType, err := commitType.ToLocalWitnessType() + if err != nil { + return hint, nil, err + } + + toRemoteWitnessType, err := commitType.ToRemoteWitnessType() if err != nil { return hint, nil, err } @@ -380,14 +347,9 @@ func (t *backupTask) craftSessionPayload( // using the input's witness type to select the appropriate // field switch inp.WitnessType() { - case input.CommitmentRevoke: + case toLocalWitnessType: justiceKit.CommitToLocalSig = signature - - case input.CommitSpendNoDelayTweakless: - fallthrough - case input.CommitmentNoDelay: - fallthrough - case input.CommitmentToRemoteConfirmed: + case toRemoteWitnessType: justiceKit.CommitToRemoteSig = signature default: return hint, nil, fmt.Errorf("invalid witness type: %v", diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index 4639b8164..bfce2811d 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -72,6 +72,7 @@ type backupTaskTest struct { // corresponding BreachInfo, as well as setting the wtpolicy.Policy of the given // session. func genTaskTest( + t *testing.T, name string, stateNum uint64, toLocalAmt int64, @@ -91,15 +92,12 @@ func genTaskTest( } // Parse the key pairs for all keys used in the test. - revSK, revPK := btcec.PrivKeyFromBytes( - revPrivBytes, - ) - _, toLocalPK := btcec.PrivKeyFromBytes( - toLocalPrivBytes, - ) - toRemoteSK, toRemotePK := btcec.PrivKeyFromBytes( - toRemotePrivBytes, - ) + revSK, revPK := btcec.PrivKeyFromBytes(revPrivBytes) + _, toLocalPK := btcec.PrivKeyFromBytes(toLocalPrivBytes) + toRemoteSK, toRemotePK := btcec.PrivKeyFromBytes(toRemotePrivBytes) + + commitType, err := blobType.CommitmentType(&chanType) + require.NoError(t, err) // Create the signer, and add the revocation and to-remote privkeys. signer := wtmock.NewMockSigner() @@ -174,12 +172,9 @@ func genTaskTest( Hash: txid, Index: index, } - toLocalInput = input.NewBaseInput( - &breachInfo.RemoteOutpoint, - input.CommitmentRevoke, - breachInfo.RemoteOutputSignDesc, - 0, - ) + toLocalInput, err = commitType.ToLocalInput(breachInfo) + require.NoError(t, err) + index++ } if toRemoteAmt > 0 { @@ -188,31 +183,8 @@ func genTaskTest( Index: index, } - var witnessType input.WitnessType - switch { - case chanType.HasAnchors(): - witnessType = input.CommitmentToRemoteConfirmed - case chanType.IsTweakless(): - witnessType = input.CommitSpendNoDelayTweakless - default: - witnessType = input.CommitmentNoDelay - } - - if chanType.HasAnchors() { - toRemoteInput = input.NewCsvInput( - &breachInfo.LocalOutpoint, - witnessType, - breachInfo.LocalOutputSignDesc, - 0, 1, - ) - } else { - toRemoteInput = input.NewBaseInput( - &breachInfo.LocalOutpoint, - witnessType, - breachInfo.LocalOutputSignDesc, - 0, - ) - } + toRemoteInput, err = commitType.ToRemoteInput(breachInfo) + require.NoError(t, err) } return backupTaskTest{ @@ -312,6 +284,7 @@ func TestBackupTask(t *testing.T) { backupTaskTests = append(backupTaskTests, []backupTaskTest{ genTaskTest( + t, "commit no-reward, both outputs", 100, // stateNum 200000, // toLocalAmt @@ -325,6 +298,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit no-reward, to-local output only", 1000, // stateNum 200000, // toLocalAmt @@ -338,6 +312,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit no-reward, to-remote output only", 1, // stateNum 0, // toLocalAmt @@ -351,6 +326,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit no-reward, to-remote output only, creates dust", 1, // stateNum 0, // toLocalAmt @@ -364,6 +340,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit no-reward, no outputs, fee rate exceeds inputs", 300, // stateNum 0, // toLocalAmt @@ -377,6 +354,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit no-reward, no outputs, fee rate of 0 creates dust", 300, // stateNum 0, // toLocalAmt @@ -390,6 +368,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit reward, both outputs", 100, // stateNum 200000, // toLocalAmt @@ -403,6 +382,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit reward, to-local output only", 1000, // stateNum 200000, // toLocalAmt @@ -416,6 +396,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit reward, to-remote output only", 1, // stateNum 0, // toLocalAmt @@ -429,6 +410,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit reward, to-remote output only, creates dust", 1, // stateNum 0, // toLocalAmt @@ -442,6 +424,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit reward, no outputs, fee rate exceeds inputs", 300, // stateNum 0, // toLocalAmt @@ -455,6 +438,7 @@ func TestBackupTask(t *testing.T) { chanType, ), genTaskTest( + t, "commit reward, no outputs, fee rate of 0 creates dust", 300, // stateNum 0, // toLocalAmt