contractcourt: store htlc timeout sweeps

This commit is contained in:
carla 2020-07-07 19:49:57 +02:00
parent a38dc256fd
commit d0ec872ef3
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
2 changed files with 97 additions and 7 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
@ -275,6 +276,8 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
}
}
var spendTxID *chainhash.Hash
// waitForOutputResolution waits for the HTLC output to be fully
// resolved. The output is considered fully resolved once it has been
// spent, and the spending transaction has been fully confirmed.
@ -291,10 +294,11 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
}
select {
case _, ok := <-spendNtfn.Spend:
case spendDetail, ok := <-spendNtfn.Spend:
if !ok {
return errResolverShuttingDown
}
spendTxID = spendDetail.SpenderTxHash
case <-h.quit:
return errResolverShuttingDown
@ -333,6 +337,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
if !ok {
return nil, errResolverShuttingDown
}
spendTxID = spend.SpenderTxHash
case <-h.quit:
return nil, errResolverShuttingDown
@ -365,6 +370,8 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
return nil, err
}
var 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.
@ -374,12 +381,35 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
if err := waitForOutputResolution(); err != nil {
return nil, err
}
// 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, and wait.
// resolved, record the timeout and the sweep txid on disk, and wait.
h.resolved = true
return nil, h.Checkpoint(h)
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

View File

@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
@ -129,6 +130,10 @@ func TestHtlcTimeoutResolver(t *testing.T) {
// can use this to customize the witness used when spending to
// trigger various redemption cases.
txToBroadcast func() (*wire.MsgTx, error)
// outcome is the resolver outcome that we expect to be reported
// once the contract is fully resolved.
outcome channeldb.ResolverOutcome
}{
// Remote commitment is broadcast, we time out the HTLC on
// chain, and should expect a fail HTLC resolution.
@ -148,6 +153,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
templateTx.TxIn[0].Witness = witness
return templateTx, nil
},
outcome: channeldb.ResolverOutcomeTimeout,
},
// Our local commitment is broadcast, we timeout the HTLC and
@ -166,8 +172,13 @@ func TestHtlcTimeoutResolver(t *testing.T) {
}
templateTx.TxIn[0].Witness = witness
// Set the outpoint to be on our commitment, since
// we need to claim in two stages.
templateTx.TxIn[0].PreviousOutPoint = testChanPoint1
return templateTx, nil
},
outcome: channeldb.ResolverOutcomeTimeout,
},
// The remote commitment is broadcast, they sweep with the
@ -189,6 +200,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
templateTx.TxIn[0].Witness = witness
return templateTx, nil
},
outcome: channeldb.ResolverOutcomeClaimed,
},
// The local commitment is broadcast, they sweep it with a
@ -210,6 +222,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
templateTx.TxIn[0].Witness = witness
return templateTx, nil
},
outcome: channeldb.ResolverOutcomeClaimed,
},
}
@ -226,6 +239,7 @@ func TestHtlcTimeoutResolver(t *testing.T) {
checkPointChan := make(chan struct{}, 1)
incubateChan := make(chan struct{}, 1)
resolutionChan := make(chan ResolutionMsg, 1)
reportChan := make(chan *channeldb.ResolverReport)
chainCfg := ChannelArbitratorConfig{
ChainArbitratorConfig: ChainArbitratorConfig{
@ -260,24 +274,49 @@ func TestHtlcTimeoutResolver(t *testing.T) {
cfg := ResolverConfig{
ChannelArbitratorConfig: chainCfg,
Checkpoint: func(_ ContractResolver,
_ ...*channeldb.ResolverReport) error {
reports ...*channeldb.ResolverReport) error {
checkPointChan <- struct{}{}
// Send all of our reports into the channel.
for _, report := range reports {
reportChan <- report
}
return nil
},
}
resolver := &htlcTimeoutResolver{
htlcResolution: lnwallet.OutgoingHtlcResolution{
ClaimOutpoint: testChanPoint2,
SweepSignDesc: *fakeSignDesc,
},
contractResolverKit: *newContractResolverKit(
cfg,
),
htlc: channeldb.HTLC{
Amt: testHtlcAmt,
},
}
resolver.htlcResolution.SweepSignDesc = *fakeSignDesc
var reports []*channeldb.ResolverReport
// If the test case needs the remote commitment to be
// broadcast, then we'll set the timeout commit to a fake
// transaction to force the code path.
if !testCase.remoteCommit {
resolver.htlcResolution.SignedTimeoutTx = sweepTx
if testCase.timeout {
success := sweepTx.TxHash()
reports = append(reports, &channeldb.ResolverReport{
OutPoint: sweepTx.TxIn[0].PreviousOutPoint,
Amount: testHtlcAmt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &success,
})
}
}
// With all the setup above complete, we can initiate the
@ -312,9 +351,12 @@ func TestHtlcTimeoutResolver(t *testing.T) {
if err != nil {
t.Fatalf("unable to generate tx: %v", err)
}
spendTxHash := spendingTx.TxHash()
select {
case notifier.spendChan <- &chainntnfs.SpendDetail{
SpendingTx: spendingTx,
SpendingTx: spendingTx,
SpenderTxHash: &spendTxHash,
}:
case <-time.After(time.Second * 5):
t.Fatalf("failed to request spend ntfn")
@ -370,7 +412,8 @@ func TestHtlcTimeoutResolver(t *testing.T) {
if !testCase.remoteCommit {
select {
case notifier.spendChan <- &chainntnfs.SpendDetail{
SpendingTx: spendingTx,
SpendingTx: spendingTx,
SpenderTxHash: &spendTxHash,
}:
case <-time.After(time.Second * 5):
t.Fatalf("failed to request spend ntfn")
@ -388,6 +431,23 @@ func TestHtlcTimeoutResolver(t *testing.T) {
t.Fatalf("check point not received")
}
// Add a report to our set of expected reports with the outcome
// that the test specifies (either success or timeout).
spendTxID := spendingTx.TxHash()
amt := btcutil.Amount(fakeSignDesc.Output.Value)
reports = append(reports, &channeldb.ResolverReport{
OutPoint: testChanPoint2,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: testCase.outcome,
SpendTxID: &spendTxID,
})
for _, report := range reports {
assertResolverReport(t, reportChan, report)
}
wg.Wait()
// Finally, the resolver should be marked as resolved.