watchtower+htlcswitch: update client tower logic to recognize safu commitments
In this commit, we update the tower+link logic to tag a commitment as the new (tweakless) format if it applies. In order to do this, the BackupTask method has gained an additional parameter to indicate the type of commitment that we're attempting to upload. This new tweakless bool is then threaded through all the way to back up task creation to ensure that we make the proper input.Input. Finally, we've added a new test case for each existing test case to test each case w/ and w/o the tweakless modifier.
This commit is contained in:
parent
d22f2a1936
commit
4b65aea306
@ -175,6 +175,8 @@ type TowerClient interface {
|
||||
// state. If the method returns nil, the backup is guaranteed to be
|
||||
// successful unless the tower is unavailable and client is force quit,
|
||||
// or the justice transaction would create dust outputs when trying to
|
||||
// abide by the negotiated policy.
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution) error
|
||||
// abide by the negotiated policy. If the channel we're trying to back
|
||||
// up doesn't have a tweak for the remote party's output, then
|
||||
// isTweakless should be true.
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution, bool) error
|
||||
}
|
||||
|
@ -954,7 +954,7 @@ func (l *channelLink) htlcManager() {
|
||||
|
||||
l.fail(
|
||||
LinkFailureError{
|
||||
code: ErrSyncError,
|
||||
code: ErrRecoveryError,
|
||||
ForceClose: false,
|
||||
},
|
||||
"unable to synchronize channel "+
|
||||
@ -1844,8 +1844,13 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
chanType := l.channel.State().ChanType
|
||||
isTweakless := chanType == channeldb.SingleFunderTweakless
|
||||
|
||||
chanID := l.ChanID()
|
||||
err = l.cfg.TowerClient.BackupState(&chanID, breachInfo)
|
||||
err = l.cfg.TowerClient.BackupState(
|
||||
&chanID, breachInfo, isTweakless,
|
||||
)
|
||||
if err != nil {
|
||||
l.fail(LinkFailureError{code: ErrInternalError},
|
||||
"unable to queue breach backup: %v", err)
|
||||
|
@ -34,6 +34,11 @@ const (
|
||||
// ErrInvalidRevocation indicates that the remote peer send us an
|
||||
// invalid revocation message.
|
||||
ErrInvalidRevocation
|
||||
|
||||
// ErrRecoveryError the channel was unable to be resumed, we need the
|
||||
// remote party to force close the channel out on chain now as a
|
||||
// result.
|
||||
ErrRecoveryError
|
||||
)
|
||||
|
||||
// LinkFailureError encapsulates an error that will make us fail the current
|
||||
@ -74,6 +79,8 @@ func (e LinkFailureError) Error() string {
|
||||
return "invalid commitment"
|
||||
case ErrInvalidRevocation:
|
||||
return "invalid revocation"
|
||||
case ErrRecoveryError:
|
||||
return "unable to resume channel, recovery required"
|
||||
default:
|
||||
return "unknown error"
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
||||
// DER-encoded signature under the to-remote pubkey. The sighash flag is
|
||||
// also present, so we trim it.
|
||||
toRemoteWitness, err := input.CommitSpendNoDelay(
|
||||
signer, toRemoteSignDesc, justiceTxn,
|
||||
signer, toRemoteSignDesc, justiceTxn, false,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sign to-remote input: %v", err)
|
||||
|
@ -54,7 +54,7 @@ type backupTask struct {
|
||||
// variables.
|
||||
func newBackupTask(chanID *lnwire.ChannelID,
|
||||
breachInfo *lnwallet.BreachRetribution,
|
||||
sweepPkScript []byte) *backupTask {
|
||||
sweepPkScript []byte, isTweakless bool) *backupTask {
|
||||
|
||||
// Parse the non-dust outputs from the breach transaction,
|
||||
// simultaneously computing the total amount contained in the inputs
|
||||
@ -85,12 +85,18 @@ func newBackupTask(chanID *lnwire.ChannelID,
|
||||
totalAmt += breachInfo.RemoteOutputSignDesc.Output.Value
|
||||
}
|
||||
if breachInfo.LocalOutputSignDesc != nil {
|
||||
witnessType := input.CommitmentNoDelay
|
||||
if isTweakless {
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
input.CommitmentNoDelay,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
|
||||
totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
|
||||
}
|
||||
|
||||
@ -271,6 +277,8 @@ func (t *backupTask) craftSessionPayload(
|
||||
case input.CommitmentRevoke:
|
||||
copy(justiceKit.CommitToLocalSig[:], signature[:])
|
||||
|
||||
case input.CommitSpendNoDelayTweakless:
|
||||
fallthrough
|
||||
case input.CommitmentNoDelay:
|
||||
copy(justiceKit.CommitToRemoteSig[:], signature[:])
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ type backupTaskTest struct {
|
||||
bindErr error
|
||||
expSweepScript []byte
|
||||
signer input.Signer
|
||||
tweakless bool
|
||||
}
|
||||
|
||||
// genTaskTest creates a instance of a backupTaskTest using the passed
|
||||
@ -89,7 +90,8 @@ func genTaskTest(
|
||||
rewardScript []byte,
|
||||
expSweepAmt int64,
|
||||
expRewardAmt int64,
|
||||
bindErr error) backupTaskTest {
|
||||
bindErr error,
|
||||
tweakless bool) backupTaskTest {
|
||||
|
||||
// Parse the key pairs for all keys used in the test.
|
||||
revSK, revPK := btcec.PrivKeyFromBytes(
|
||||
@ -188,9 +190,15 @@ func genTaskTest(
|
||||
Hash: txid,
|
||||
Index: index,
|
||||
}
|
||||
|
||||
witnessType := input.CommitmentNoDelay
|
||||
if tweakless {
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
input.CommitmentNoDelay,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
@ -218,6 +226,7 @@ func genTaskTest(
|
||||
bindErr: bindErr,
|
||||
expSweepScript: makeAddrSlice(22),
|
||||
signer: signer,
|
||||
tweakless: tweakless,
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +242,19 @@ var (
|
||||
addrScript, _ = txscript.PayToAddrScript(addr)
|
||||
)
|
||||
|
||||
var backupTaskTests = []backupTaskTest{
|
||||
// TestBackupTaskBind tests the initialization and binding of a backupTask to a
|
||||
// ClientSession. After a successful bind, all parameters of the justice
|
||||
// transaction should be solidified, so we assert there correctness. In an
|
||||
// unsuccessful bind, the session-dependent parameters should be unmodified so
|
||||
// that the backup task can be rescheduled if necessary. Finally, we assert that
|
||||
// the backup task is able to encrypt a valid justice kit, and that we can
|
||||
// decrypt it using the breach txid.
|
||||
func TestBackupTask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var backupTaskTests []backupTaskTest
|
||||
for _, tweakless := range []bool{true, false} {
|
||||
backupTaskTests = append(backupTaskTests, []backupTaskTest{
|
||||
genTaskTest(
|
||||
"commit no-reward, both outputs",
|
||||
100, // stateNum
|
||||
@ -245,6 +266,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
299241, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-local output only",
|
||||
@ -257,6 +279,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
199514, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-remote output only",
|
||||
@ -269,6 +292,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
99561, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-remote output only, creates dust",
|
||||
@ -281,6 +305,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate exceeds inputs",
|
||||
@ -293,6 +318,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate of 0 creates dust",
|
||||
@ -305,6 +331,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, both outputs",
|
||||
@ -317,6 +344,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
296117, // expSweepAmt
|
||||
3000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-local output only",
|
||||
@ -329,6 +357,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
197390, // expSweepAmt
|
||||
2000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-remote output only",
|
||||
@ -341,6 +370,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
98437, // expSweepAmt
|
||||
1000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-remote output only, creates dust",
|
||||
@ -353,6 +383,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate exceeds inputs",
|
||||
@ -365,6 +396,7 @@ var backupTaskTests = []backupTaskTest{
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate of 0 creates dust",
|
||||
@ -377,21 +409,17 @@ var backupTaskTests = []backupTaskTest{
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
}...)
|
||||
}
|
||||
|
||||
// TestBackupTaskBind tests the initialization and binding of a backupTask to a
|
||||
// ClientSession. After a successful bind, all parameters of the justice
|
||||
// transaction should be solidified, so we assert there correctness. In an
|
||||
// unsuccessful bind, the session-dependent parameters should be unmodified so
|
||||
// that the backup task can be rescheduled if necessary. Finally, we assert that
|
||||
// the backup task is able to encrypt a valid justice kit, and that we can
|
||||
// decrypt it using the breach txid.
|
||||
func TestBackupTask(t *testing.T) {
|
||||
for _, test := range backupTaskTests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range backupTaskTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testBackupTask(t, test)
|
||||
})
|
||||
}
|
||||
@ -399,7 +427,10 @@ func TestBackupTask(t *testing.T) {
|
||||
|
||||
func testBackupTask(t *testing.T, test backupTaskTest) {
|
||||
// Create a new backupTask from the channel id and breach info.
|
||||
task := newBackupTask(&test.chanID, test.breachInfo, test.expSweepScript)
|
||||
task := newBackupTask(
|
||||
&test.chanID, test.breachInfo, test.expSweepScript,
|
||||
test.tweakless,
|
||||
)
|
||||
|
||||
// Assert that all parameters set during initialization are properly
|
||||
// populated.
|
||||
|
@ -90,8 +90,10 @@ type Client interface {
|
||||
// state. If the method returns nil, the backup is guaranteed to be
|
||||
// successful unless the client is force quit, or the justice
|
||||
// transaction would create dust outputs when trying to abide by the
|
||||
// negotiated policy.
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution) error
|
||||
// negotiated policy. If the channel we're trying to back up doesn't
|
||||
// have a tweak for the remote party's output, then isTweakless should
|
||||
// be true.
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution, bool) error
|
||||
|
||||
// Start initializes the watchtower client, allowing it process requests
|
||||
// to backup revoked channel states.
|
||||
@ -564,7 +566,7 @@ func (c *TowerClient) RegisterChannel(chanID lnwire.ChannelID) error {
|
||||
// - breached outputs contain too little value to sweep at the target sweep fee
|
||||
// rate.
|
||||
func (c *TowerClient) BackupState(chanID *lnwire.ChannelID,
|
||||
breachInfo *lnwallet.BreachRetribution) error {
|
||||
breachInfo *lnwallet.BreachRetribution, isTweakless bool) error {
|
||||
|
||||
// Retrieve the cached sweep pkscript used for this channel.
|
||||
c.backupMu.Lock()
|
||||
@ -589,7 +591,9 @@ func (c *TowerClient) BackupState(chanID *lnwire.ChannelID,
|
||||
c.chanCommitHeights[*chanID] = breachInfo.RevokedStateNum
|
||||
c.backupMu.Unlock()
|
||||
|
||||
task := newBackupTask(chanID, breachInfo, summary.SweepPkScript)
|
||||
task := newBackupTask(
|
||||
chanID, breachInfo, summary.SweepPkScript, isTweakless,
|
||||
)
|
||||
|
||||
return c.pipeline.QueueBackupTask(task)
|
||||
}
|
||||
|
@ -628,7 +628,7 @@ func (h *testHarness) backupState(id, i uint64, expErr error) {
|
||||
_, retribution := h.channel(id).getState(i)
|
||||
|
||||
chanID := chanIDFromInt(id)
|
||||
err := h.client.BackupState(&chanID, retribution)
|
||||
err := h.client.BackupState(&chanID, retribution, false)
|
||||
if err != expErr {
|
||||
h.t.Fatalf("back error mismatch, want: %v, got: %v",
|
||||
expErr, err)
|
||||
|
Loading…
Reference in New Issue
Block a user