From d0ec872ef31cfbdad1eed7c71893502cb835b6f4 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 7 Jul 2020 19:49:57 +0200 Subject: [PATCH] contractcourt: store htlc timeout sweeps --- contractcourt/htlc_timeout_resolver.go | 36 ++++++++++- contractcourt/htlc_timeout_resolver_test.go | 68 +++++++++++++++++++-- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index 83c33435..b85441b1 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -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 diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index 293db46f..fdf0bba5 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -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.