contractcourt/timeout_resolver: extract logic into sweepSecondLevelTransaction

This commit moves the logic for sweeping the confirmed second-level
timeout transaction into its own method.

We do a small change to the logic: When setting the spending tx in the
report, we use the detected commitspend instead of the presigned tiemout
tx. This is to prepare for the coming change where the spending
transaction might actually be a re-signed timeout tx, and will therefore
have a different txid.
This commit is contained in:
Johan T. Halseth 2020-12-09 12:24:03 +01:00
parent 2f33425509
commit 0c3b64a3cd
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
2 changed files with 118 additions and 54 deletions

View File

@ -261,8 +261,6 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
return nil, err return nil, err
} }
spendTxID := commitSpend.SpenderTxHash
// If the spend reveals the pre-image, then we'll enter the clean up // If the spend reveals the pre-image, then we'll enter the clean up
// workflow to pass the pre-image back to the incoming link, add it to // workflow to pass the pre-image back to the incoming link, add it to
// the witness cache, and exit. // the witness cache, and exit.
@ -290,54 +288,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
return nil, err return nil, err
} }
var reports []*channeldb.ResolverReport return h.sweepSecondLevelTransaction(commitSpend)
// Finally, if this was an output on our commitment transaction, we'll
// wait for the second-level HTLC output to be spent, and for that
// transaction itself to confirm.
if h.htlcResolution.SignedTimeoutTx != nil {
log.Infof("%T(%v): waiting for nursery to spend CSV delayed "+
"output", h, h.htlcResolution.ClaimOutpoint)
sweep, err := waitForSpend(
&h.htlcResolution.ClaimOutpoint,
h.htlcResolution.SweepSignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return nil, err
}
// Update the spend txid to the hash of the sweep transaction.
spendTxID = sweep.SpenderTxHash
// Once our timeout tx has confirmed, we add a resolution for
// our timeoutTx tx first stage transaction.
timeoutTx := h.htlcResolution.SignedTimeoutTx
spendHash := timeoutTx.TxHash()
reports = append(reports, &channeldb.ResolverReport{
OutPoint: timeoutTx.TxIn[0].PreviousOutPoint,
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &spendHash,
})
}
// With the clean up message sent, we'll now mark the contract
// resolved, record the timeout and the sweep txid on disk, and wait.
h.resolved = true
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
reports = append(reports, &channeldb.ResolverReport{
OutPoint: h.htlcResolution.ClaimOutpoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
SpendTxID: spendTxID,
})
return nil, h.Checkpoint(h, reports...)
} }
// spendHtlcOutput handles the initial spend of an HTLC output via the timeout // spendHtlcOutput handles the initial spend of an HTLC output via the timeout
@ -394,6 +345,71 @@ func (h *htlcTimeoutResolver) spendHtlcOutput() (*chainntnfs.SpendDetail, error)
return spend, err return spend, err
} }
// sweepSecondLevelTransaction sweeps the output of the confirmed second-level
// timeout transaction into our wallet. The given SpendDetail should be the
// confirmed timeout tx spending the HTLC output on the commitment tx.
func (h *htlcTimeoutResolver) sweepSecondLevelTransaction(
commitSpend *chainntnfs.SpendDetail) (ContractResolver, error) {
var (
// spendTxID will be the ultimate spend of the claimOutpoint.
// We set it to the commit spend for now, as this is the
// ultimate spend in case this is a remote commitment. If we go
// through the second-level transaction, we'll update this
// accordingly.
spendTxID = commitSpend.SpenderTxHash
reports []*channeldb.ResolverReport
)
// Finally, if this was an output on our commitment transaction, we'll
// wait for the second-level HTLC output to be spent, and for that
// transaction itself to confirm.
if h.htlcResolution.SignedTimeoutTx != nil {
log.Infof("%T(%v): waiting for nursery to spend CSV delayed "+
"output", h, h.htlcResolution.ClaimOutpoint)
sweep, err := waitForSpend(
&h.htlcResolution.ClaimOutpoint,
h.htlcResolution.SweepSignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return nil, err
}
// Update the spend txid to the hash of the sweep transaction.
spendTxID = sweep.SpenderTxHash
// Once our sweep of the timeout tx has confirmed, we add a
// resolution for our timeoutTx tx first stage transaction.
timeoutTx := commitSpend.SpendingTx
spendHash := timeoutTx.TxHash()
reports = append(reports, &channeldb.ResolverReport{
OutPoint: timeoutTx.TxIn[0].PreviousOutPoint,
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &spendHash,
})
}
// With the clean up message sent, we'll now mark the contract
// resolved, record the timeout and the sweep txid on disk, and wait.
h.resolved = true
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
reports = append(reports, &channeldb.ResolverReport{
OutPoint: h.htlcResolution.ClaimOutpoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
SpendTxID: spendTxID,
})
return nil, h.Checkpoint(h, reports...)
}
// Stop signals the resolver to cancel any current resolution processes, and // Stop signals the resolver to cancel any current resolution processes, and
// suspend. // suspend.
// //

View File

@ -3,6 +3,7 @@ package contractcourt
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"reflect"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -17,6 +18,7 @@ import (
"github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require"
) )
type mockWitnessBeacon struct { type mockWitnessBeacon struct {
@ -127,6 +129,16 @@ func TestHtlcTimeoutResolver(t *testing.T) {
return nil, err return nil, err
} }
// To avoid triggering the race detector by
// setting the witness the second time this
// method is called during tests, we return
// immediately if the witness is already set
// correctly.
if reflect.DeepEqual(
templateTx.TxIn[0].Witness, witness,
) {
return templateTx, nil
}
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
return templateTx, nil return templateTx, nil
}, },
@ -148,6 +160,17 @@ func TestHtlcTimeoutResolver(t *testing.T) {
return nil, err return nil, err
} }
// To avoid triggering the race detector by
// setting the witness the second time this
// method is called during tests, we return
// immediately if the witness is already set
// correctly.
if reflect.DeepEqual(
templateTx.TxIn[0].Witness, witness,
) {
return templateTx, nil
}
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
// Set the outpoint to be on our commitment, since // Set the outpoint to be on our commitment, since
@ -174,6 +197,17 @@ func TestHtlcTimeoutResolver(t *testing.T) {
return nil, err return nil, err
} }
// To avoid triggering the race detector by
// setting the witness the second time this
// method is called during tests, we return
// immediately if the witness is already set
// correctly.
if reflect.DeepEqual(
templateTx.TxIn[0].Witness, witness,
) {
return templateTx, nil
}
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
return templateTx, nil return templateTx, nil
}, },
@ -196,6 +230,17 @@ func TestHtlcTimeoutResolver(t *testing.T) {
return nil, err return nil, err
} }
// To avoid triggering the race detector by
// setting the witness the second time this
// method is called during tests, we return
// immediately if the witness is already set
// correctly.
if reflect.DeepEqual(
templateTx.TxIn[0].Witness, witness,
) {
return templateTx, nil
}
templateTx.TxIn[0].Witness = witness templateTx.TxIn[0].Witness = witness
return templateTx, nil return templateTx, nil
}, },
@ -282,16 +327,19 @@ func TestHtlcTimeoutResolver(t *testing.T) {
// broadcast, then we'll set the timeout commit to a fake // broadcast, then we'll set the timeout commit to a fake
// transaction to force the code path. // transaction to force the code path.
if !testCase.remoteCommit { if !testCase.remoteCommit {
resolver.htlcResolution.SignedTimeoutTx = sweepTx timeoutTx, err := testCase.txToBroadcast()
require.NoError(t, err)
resolver.htlcResolution.SignedTimeoutTx = timeoutTx
if testCase.timeout { if testCase.timeout {
success := sweepTx.TxHash() timeoutTxID := timeoutTx.TxHash()
reports = append(reports, &channeldb.ResolverReport{ reports = append(reports, &channeldb.ResolverReport{
OutPoint: sweepTx.TxIn[0].PreviousOutPoint, OutPoint: timeoutTx.TxIn[0].PreviousOutPoint,
Amount: testHtlcAmt.ToSatoshis(), Amount: testHtlcAmt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc, ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage, ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &success, SpendTxID: &timeoutTxID,
}) })
} }
} }