diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index 62ff832071..3227865d19 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -538,7 +538,6 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error { return err } - // TODO(yy): checkpoint here? return err } @@ -562,6 +561,59 @@ func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error { return h.Checkpoint(h) } +// sweepDirectHtlcOutput sends the direct spend of the HTLC output to the +// sweeper. This is used when the remote party goes on chain, and we're able to +// sweep an HTLC we offered after a timeout. Only the CLTV encumbered outputs +// are resolved via this path. +func (h *htlcTimeoutResolver) sweepDirectHtlcOutput(immediate bool) error { + var htlcWitnessType input.StandardWitnessType + if h.isTaproot() { + htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout + } else { + htlcWitnessType = input.HtlcOfferedRemoteTimeout + } + + sweepInput := input.NewCsvInputWithCltv( + &h.htlcResolution.ClaimOutpoint, htlcWitnessType, + &h.htlcResolution.SweepSignDesc, h.broadcastHeight, + h.htlcResolution.CsvDelay, h.htlcResolution.Expiry, + ) + + // Calculate the budget. + // + // TODO(yy): the budget is twice the output's value, which is needed as + // we don't force sweep the output now. To prevent cascading force + // closes, we use all its output value plus a wallet input as the + // budget. This is a temporary solution until we can optionally cancel + // the incoming HTLC, more details in, + // - https://github.com/lightningnetwork/lnd/issues/7969 + budget := calculateBudget( + btcutil.Amount(sweepInput.SignDesc().Output.Value), 2, 0, + ) + + log.Infof("%T(%x): offering offered remote timeout HTLC output to "+ + "sweeper with deadline %v and budget=%v at height=%v", + h, h.htlc.RHash[:], h.incomingHTLCExpiryHeight, budget, + h.broadcastHeight) + + _, err := h.Sweeper.SweepInput( + sweepInput, + sweep.Params{ + Budget: budget, + + // This is an outgoing HTLC, so we want to make sure + // that we sweep it before the incoming HTLC expires. + DeadlineHeight: h.incomingHTLCExpiryHeight, + Immediate: immediate, + }, + ) + if err != nil { + return err + } + + return nil +} + // spendHtlcOutput handles the initial spend of an HTLC output via the timeout // clause. If this is our local commitment, the second-level timeout TX will be // used to spend the output into the next stage. If this is the remote @@ -582,8 +634,18 @@ func (h *htlcTimeoutResolver) spendHtlcOutput( return nil, err } - // If we have no SignDetails, and we haven't already sent the output to - // the utxo nursery, then we'll do so now. + // If this is a remote commitment there's no second level timeout txn, + // and we can just send this directly to the sweeper. + case h.htlcResolution.SignedTimeoutTx == nil && !h.outputIncubating: + if err := h.sweepDirectHtlcOutput(immediate); err != nil { + log.Errorf("Sending direct spend to sweeper: %v", err) + + return nil, err + } + + // If we have a SignedTimeoutTx but no SignDetails, this is a local + // commitment for a non-anchor channel, so we'll send it to the utxo + // nursery. case h.htlcResolution.SignDetails == nil && !h.outputIncubating: if err := h.sendSecondLevelTxLegacy(); err != nil { log.Errorf("Sending timeout tx to nursery: %v", err) @@ -690,6 +752,13 @@ func (h *htlcTimeoutResolver) handleCommitSpend( ) switch { + + // If we swept an HTLC directly off the remote party's commitment + // transaction, then we can exit here as there's no second level sweep + // to do. + case h.htlcResolution.SignedTimeoutTx == nil: + break + // If the sweeper is handling the second level transaction, wait for // the CSV and possible CLTV lock to expire, before sweeping the output // on the second-level. @@ -763,6 +832,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend( h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight), h.htlc.RHash, ) + // Calculate the budget for this sweep. budget := calculateBudget( btcutil.Amount(inp.SignDesc().Output.Value), @@ -800,6 +870,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend( case h.htlcResolution.SignedTimeoutTx != nil: log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+ "delayed output", h, claimOutpoint) + sweepTx, err := waitForSpend( &claimOutpoint, h.htlcResolution.SweepSignDesc.Output.PkScript, @@ -866,9 +937,11 @@ func (h *htlcTimeoutResolver) IsResolved() bool { // report returns a report on the resolution state of the contract. func (h *htlcTimeoutResolver) report() *ContractReport { - // If the sign details are nil, the report will be created by handled - // by the nursery. - if h.htlcResolution.SignDetails == nil { + // If we have a SignedTimeoutTx but no SignDetails, this is a local + // commitment for a non-anchor channel, which was handled by the utxo + // nursery. + if h.htlcResolution.SignDetails == nil && h. + htlcResolution.SignedTimeoutTx != nil { return nil } @@ -888,13 +961,20 @@ func (h *htlcTimeoutResolver) initReport() { ) } + // If there's no timeout transaction, then we're already effectively in + // level two. + stage := uint32(1) + if h.htlcResolution.SignedTimeoutTx == nil { + stage = 2 + } + h.currentReport = ContractReport{ Outpoint: h.htlcResolution.ClaimOutpoint, Type: ReportOutputOutgoingHtlc, Amount: finalAmt, MaturityHeight: h.htlcResolution.Expiry, LimboBalance: finalAmt, - Stage: 1, + Stage: stage, } } diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index c551a6f1ce..47be71d3ec 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -69,11 +69,31 @@ func (m *mockWitnessBeacon) AddPreimages(preimages ...lntypes.Preimage) error { return nil } -// TestHtlcTimeoutResolver tests that the timeout resolver properly handles all -// variations of possible local+remote spends. -func TestHtlcTimeoutResolver(t *testing.T) { - t.Parallel() +type htlcTimeoutTestCase struct { + // name is a human readable description of the test case. + name string + + // remoteCommit denotes if the commitment broadcast was the remote + // commitment or not. + remoteCommit bool + + // timeout denotes if the HTLC should be let timeout, or if the "remote" + // party should sweep it on-chain. This also affects what type of + // resolution message we expect. + timeout bool + + // txToBroadcast is a function closure that should generate the + // transaction that should spend the HTLC output. Test authors 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 +} +func genHtlcTimeoutTestCases() []htlcTimeoutTestCase { fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize) var ( @@ -105,29 +125,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { }, } - testCases := []struct { - // name is a human readable description of the test case. - name string - - // remoteCommit denotes if the commitment broadcast was the - // remote commitment or not. - remoteCommit bool - - // timeout denotes if the HTLC should be let timeout, or if the - // "remote" party should sweep it on-chain. This also affects - // what type of resolution message we expect. - timeout bool - - // txToBroadcast is a function closure that should generate the - // transaction that should spend the HTLC output. Test authors - // 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 - }{ + return []htlcTimeoutTestCase{ // Remote commitment is broadcast, we time out the HTLC on // chain, and should expect a fail HTLC resolution. { @@ -149,7 +147,8 @@ func TestHtlcTimeoutResolver(t *testing.T) { // immediately if the witness is already set // correctly. if reflect.DeepEqual( - templateTx.TxIn[0].Witness, witness, + templateTx.TxIn[0].Witness, + witness, ) { return templateTx, nil @@ -219,7 +218,8 @@ func TestHtlcTimeoutResolver(t *testing.T) { // immediately if the witness is already set // correctly. if reflect.DeepEqual( - templateTx.TxIn[0].Witness, witness, + templateTx.TxIn[0].Witness, + witness, ) { return templateTx, nil @@ -253,7 +253,8 @@ func TestHtlcTimeoutResolver(t *testing.T) { // immediately if the witness is already set // correctly. if reflect.DeepEqual( - templateTx.TxIn[0].Witness, witness, + templateTx.TxIn[0].Witness, + witness, ) { return templateTx, nil @@ -265,243 +266,280 @@ func TestHtlcTimeoutResolver(t *testing.T) { outcome: channeldb.ResolverOutcomeClaimed, }, } +} + +func testHtlcTimeoutResolver(t *testing.T, testCase htlcTimeoutTestCase) { + fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize) + var fakePreimage lntypes.Preimage + + fakeSignDesc := &input.SignDescriptor{ + Output: &wire.TxOut{}, + } + + copy(fakePreimage[:], fakePreimageBytes) notifier := &mock.ChainNotifier{ EpochChan: make(chan *chainntnfs.BlockEpoch), SpendChan: make(chan *chainntnfs.SpendDetail), ConfChan: make(chan *chainntnfs.TxConfirmation), } + witnessBeacon := newMockWitnessBeacon() + checkPointChan := make(chan struct{}, 1) + incubateChan := make(chan struct{}, 1) + resolutionChan := make(chan ResolutionMsg, 1) + reportChan := make(chan *channeldb.ResolverReport) + + //nolint:lll + chainCfg := ChannelArbitratorConfig{ + ChainArbitratorConfig: ChainArbitratorConfig{ + Notifier: notifier, + Sweeper: newMockSweeper(), + PreimageDB: witnessBeacon, + IncubateOutputs: func(wire.OutPoint, + fn.Option[lnwallet.OutgoingHtlcResolution], + fn.Option[lnwallet.IncomingHtlcResolution], + uint32, fn.Option[int32]) error { + + incubateChan <- struct{}{} + return nil + }, + DeliverResolutionMsg: func(msgs ...ResolutionMsg) error { + if len(msgs) != 1 { + return fmt.Errorf("expected 1 "+ + "resolution msg, instead got %v", + len(msgs)) + } - for _, testCase := range testCases { - t.Logf("Running test case: %v", testCase.name) - - checkPointChan := make(chan struct{}, 1) - incubateChan := make(chan struct{}, 1) - resolutionChan := make(chan ResolutionMsg, 1) - reportChan := make(chan *channeldb.ResolverReport) - - //nolint:lll - chainCfg := ChannelArbitratorConfig{ - ChainArbitratorConfig: ChainArbitratorConfig{ - Notifier: notifier, - PreimageDB: witnessBeacon, - IncubateOutputs: func(wire.OutPoint, - fn.Option[lnwallet.OutgoingHtlcResolution], - fn.Option[lnwallet.IncomingHtlcResolution], - uint32, fn.Option[int32]) error { - - incubateChan <- struct{}{} - return nil - }, - DeliverResolutionMsg: func(msgs ...ResolutionMsg) error { - if len(msgs) != 1 { - return fmt.Errorf("expected 1 "+ - "resolution msg, instead got %v", - len(msgs)) - } + resolutionChan <- msgs[0] - resolutionChan <- msgs[0] - return nil - }, - Budget: *DefaultBudgetConfig(), - QueryIncomingCircuit: func(circuit models.CircuitKey) *models.CircuitKey { - return nil - }, + return nil }, - PutResolverReport: func(_ kvdb.RwTx, - _ *channeldb.ResolverReport) error { + Budget: *DefaultBudgetConfig(), + QueryIncomingCircuit: func(circuit models.CircuitKey, + ) *models.CircuitKey { return nil }, - } + }, + PutResolverReport: func(_ kvdb.RwTx, + _ *channeldb.ResolverReport) error { - cfg := ResolverConfig{ - ChannelArbitratorConfig: chainCfg, - Checkpoint: func(_ ContractResolver, - reports ...*channeldb.ResolverReport) error { + return nil + }, + } - checkPointChan <- struct{}{} + cfg := ResolverConfig{ + ChannelArbitratorConfig: chainCfg, + Checkpoint: func(_ ContractResolver, + reports ...*channeldb.ResolverReport) error { - // Send all of our reports into the channel. - for _, report := range reports { - reportChan <- report - } + checkPointChan <- struct{}{} - return nil - }, - } - resolver := &htlcTimeoutResolver{ - htlcResolution: lnwallet.OutgoingHtlcResolution{ - ClaimOutpoint: testChanPoint2, - SweepSignDesc: *fakeSignDesc, - }, - contractResolverKit: *newContractResolverKit( - cfg, - ), - htlc: channeldb.HTLC{ - Amt: testHtlcAmt, - }, - } + // Send all of our reports into the channel. + for _, report := range reports { + reportChan <- report + } - var reports []*channeldb.ResolverReport + return nil + }, + } + resolver := &htlcTimeoutResolver{ + htlcResolution: lnwallet.OutgoingHtlcResolution{ + ClaimOutpoint: testChanPoint2, + SweepSignDesc: *fakeSignDesc, + }, + contractResolverKit: *newContractResolverKit( + cfg, + ), + htlc: channeldb.HTLC{ + Amt: testHtlcAmt, + }, + } - // 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 { - timeoutTx, err := testCase.txToBroadcast() - require.NoError(t, err) - - resolver.htlcResolution.SignedTimeoutTx = timeoutTx - - if testCase.timeout { - timeoutTxID := timeoutTx.TxHash() - reports = append(reports, &channeldb.ResolverReport{ - OutPoint: timeoutTx.TxIn[0].PreviousOutPoint, - Amount: testHtlcAmt.ToSatoshis(), - ResolverType: channeldb.ResolverTypeOutgoingHtlc, - ResolverOutcome: channeldb.ResolverOutcomeFirstStage, - SpendTxID: &timeoutTxID, - }) + 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 { + timeoutTx, err := testCase.txToBroadcast() + require.NoError(t, err) + + resolver.htlcResolution.SignedTimeoutTx = timeoutTx + + if testCase.timeout { + timeoutTxID := timeoutTx.TxHash() + report := &channeldb.ResolverReport{ + OutPoint: timeoutTx.TxIn[0].PreviousOutPoint, //nolint:lll + Amount: testHtlcAmt.ToSatoshis(), + ResolverType: channeldb.ResolverTypeOutgoingHtlc, //nolint:lll + ResolverOutcome: channeldb.ResolverOutcomeFirstStage, //nolint:lll + SpendTxID: &timeoutTxID, } + + reports = append(reports, report) } + } - // With all the setup above complete, we can initiate the - // resolution process, and the bulk of our test. - var wg sync.WaitGroup - resolveErr := make(chan error, 1) - wg.Add(1) - go func() { - defer wg.Done() - - _, err := resolver.Resolve(false) - if err != nil { - resolveErr <- err - } - }() + // With all the setup above complete, we can initiate the + // resolution process, and the bulk of our test. + var wg sync.WaitGroup + resolveErr := make(chan error, 1) + wg.Add(1) + go func() { + defer wg.Done() + + _, err := resolver.Resolve(false) + if err != nil { + resolveErr <- err + } + }() + + // If this is a remote commit, then we expct the outputs should receive + // an incubation request to go through the sweeper, otherwise the + // nursery. + var sweepChan chan input.Input + if testCase.remoteCommit { + mockSweeper, ok := resolver.Sweeper.(*mockSweeper) + require.True(t, ok) + sweepChan = mockSweeper.sweptInputs + } + + // The output should be offered to either the sweeper or + // the nursery. + select { + case <-incubateChan: + case <-sweepChan: + case err := <-resolveErr: + t.Fatalf("unable to resolve HTLC: %v", err) + case <-time.After(time.Second * 5): + t.Fatalf("failed to receive incubation request") + } + + // Next, the resolver should request a spend notification for + // the direct HTLC output. We'll use the txToBroadcast closure + // for the test case to generate the transaction that we'll + // send to the resolver. + spendingTx, err := testCase.txToBroadcast() + if err != nil { + t.Fatalf("unable to generate tx: %v", err) + } + spendTxHash := spendingTx.TxHash() + + select { + case notifier.SpendChan <- &chainntnfs.SpendDetail{ + SpendingTx: spendingTx, + SpenderTxHash: &spendTxHash, + }: + case <-time.After(time.Second * 5): + t.Fatalf("failed to request spend ntfn") + } - // At the output isn't yet in the nursery, we expect that we - // should receive an incubation request. + if !testCase.timeout { + // If the resolver should settle now, then we'll + // extract the pre-image to be extracted and the + // resolution message sent. select { - case <-incubateChan: - case err := <-resolveErr: - t.Fatalf("unable to resolve HTLC: %v", err) + case newPreimage := <-witnessBeacon.newPreimages: + if newPreimage[0] != fakePreimage { + t.Fatalf("wrong pre-image: "+ + "expected %v, got %v", + fakePreimage, newPreimage) + } + case <-time.After(time.Second * 5): - t.Fatalf("failed to receive incubation request") + t.Fatalf("pre-image not added") } - // Next, the resolver should request a spend notification for - // the direct HTLC output. We'll use the txToBroadcast closure - // for the test case to generate the transaction that we'll - // send to the resolver. - spendingTx, err := testCase.txToBroadcast() - if err != nil { - t.Fatalf("unable to generate tx: %v", err) + // Finally, we should get a resolution message with the + // pre-image set within the message. + select { + case resolutionMsg := <-resolutionChan: + // Once again, the pre-images should match up. + if *resolutionMsg.PreImage != fakePreimage { + t.Fatalf("wrong pre-image: "+ + "expected %v, got %v", + fakePreimage, resolutionMsg.PreImage) + } + case <-time.After(time.Second * 5): + t.Fatalf("resolution not sent") } - spendTxHash := spendingTx.TxHash() - + } else { + // Otherwise, the HTLC should now timeout. First, we + // should get a resolution message with a populated + // failure message. select { - case notifier.SpendChan <- &chainntnfs.SpendDetail{ - SpendingTx: spendingTx, - SpenderTxHash: &spendTxHash, - }: + case resolutionMsg := <-resolutionChan: + if resolutionMsg.Failure == nil { + t.Fatalf("expected failure resolution msg") + } case <-time.After(time.Second * 5): - t.Fatalf("failed to request spend ntfn") + t.Fatalf("resolution not sent") } - if !testCase.timeout { - // If the resolver should settle now, then we'll - // extract the pre-image to be extracted and the - // resolution message sent. + // We should also get another request for the spend + // notification of the second-level transaction to + // indicate that it's been swept by the nursery, but + // only if this is a local commitment transaction. + if !testCase.remoteCommit { select { - case newPreimage := <-witnessBeacon.newPreimages: - if newPreimage[0] != fakePreimage { - t.Fatalf("wrong pre-image: "+ - "expected %v, got %v", - fakePreimage, newPreimage) - } - + case notifier.SpendChan <- &chainntnfs.SpendDetail{ + SpendingTx: spendingTx, + SpenderTxHash: &spendTxHash, + }: case <-time.After(time.Second * 5): - t.Fatalf("pre-image not added") + t.Fatalf("failed to request spend ntfn") } + } + } - // Finally, we should get a resolution message with the - // pre-image set within the message. - select { - case resolutionMsg := <-resolutionChan: - // Once again, the pre-images should match up. - if *resolutionMsg.PreImage != fakePreimage { - t.Fatalf("wrong pre-image: "+ - "expected %v, got %v", - fakePreimage, resolutionMsg.PreImage) - } - case <-time.After(time.Second * 5): - t.Fatalf("resolution not sent") - } - } else { + // In any case, before the resolver exits, it should checkpoint + // its final state. + select { + case <-checkPointChan: + case err := <-resolveErr: + t.Fatalf("unable to resolve HTLC: %v", err) + case <-time.After(time.Second * 5): + t.Fatalf("check point not received") + } - // Otherwise, the HTLC should now timeout. First, we - // should get a resolution message with a populated - // failure message. - select { - case resolutionMsg := <-resolutionChan: - if resolutionMsg.Failure == nil { - t.Fatalf("expected failure resolution msg") - } - case <-time.After(time.Second * 5): - t.Fatalf("resolution not sent") - } + // 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) - // We should also get another request for the spend - // notification of the second-level transaction to - // indicate that it's been swept by the nursery, but - // only if this is a local commitment transaction. - if !testCase.remoteCommit { - select { - case notifier.SpendChan <- &chainntnfs.SpendDetail{ - SpendingTx: spendingTx, - SpenderTxHash: &spendTxHash, - }: - case <-time.After(time.Second * 5): - t.Fatalf("failed to request spend ntfn") - } - } - } + reports = append(reports, &channeldb.ResolverReport{ + OutPoint: testChanPoint2, + Amount: amt, + ResolverType: channeldb.ResolverTypeOutgoingHtlc, + ResolverOutcome: testCase.outcome, + SpendTxID: &spendTxID, + }) - // In any case, before the resolver exits, it should checkpoint - // its final state. - select { - case <-checkPointChan: - case err := <-resolveErr: - t.Fatalf("unable to resolve HTLC: %v", err) - case <-time.After(time.Second * 5): - t.Fatalf("check point not received") - } + for _, report := range reports { + assertResolverReport(t, reportChan, report) + } - // 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, - }) + wg.Wait() - for _, report := range reports { - assertResolverReport(t, reportChan, report) - } + // Finally, the resolver should be marked as resolved. + if !resolver.resolved { + t.Fatalf("resolver should be marked as resolved") + } +} - wg.Wait() +// TestHtlcTimeoutResolver tests that the timeout resolver properly handles all +// variations of possible local+remote spends. +func TestHtlcTimeoutResolver(t *testing.T) { + t.Parallel() - // Finally, the resolver should be marked as resolved. - if !resolver.resolved { - t.Fatalf("resolver should be marked as resolved") - } + testCases := genHtlcTimeoutTestCases() + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + testHtlcTimeoutResolver(t, testCase) + }) } } @@ -536,15 +574,12 @@ func TestHtlcTimeoutSingleStage(t *testing.T) { } checkpoints := []checkpoint{ - { - // Output should be handed off to the nursery. - incubating: true, - }, { // We send a confirmation the sweep tx from published // by the nursery. preCheckpoint: func(ctx *htlcResolverTestContext, _ bool) error { + // The nursery will create and publish a sweep // tx. ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ @@ -570,7 +605,7 @@ func TestHtlcTimeoutSingleStage(t *testing.T) { // After the sweep has confirmed, we expect the // checkpoint to be resolved, and with the above // report. - incubating: true, + incubating: false, resolved: true, reports: []*channeldb.ResolverReport{ claim, @@ -653,6 +688,7 @@ func TestHtlcTimeoutSecondStage(t *testing.T) { // that our sweep succeeded. preCheckpoint: func(ctx *htlcResolverTestContext, _ bool) error { + // The nursery will publish the timeout tx. ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ SpendingTx: timeoutTx, @@ -824,9 +860,9 @@ func TestHtlcTimeoutSingleStageRemoteSpend(t *testing.T) { ) } -// TestHtlcTimeoutSecondStageRemoteSpend tests that when a remite commitment -// confirms, and the remote spends the output using the success tx, we -// properly detect this and extract the preimage. +// TestHtlcTimeoutSecondStageRemoteSpend tests that when a remote commitment +// confirms, and the remote spends the output using the success tx, we properly +// detect this and extract the preimage. func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) { commitOutpoint := wire.OutPoint{Index: 2} @@ -870,10 +906,6 @@ func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) { } checkpoints := []checkpoint{ - { - // Output should be handed off to the nursery. - incubating: true, - }, { // We send a confirmation for the remote's second layer // success transcation. @@ -919,7 +951,7 @@ func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) { // After the sweep has confirmed, we expect the // checkpoint to be resolved, and with the above // report. - incubating: true, + incubating: false, resolved: true, reports: []*channeldb.ResolverReport{ claim, @@ -1298,6 +1330,8 @@ func TestHtlcTimeoutSecondStageSweeperRemoteSpend(t *testing.T) { func testHtlcTimeout(t *testing.T, resolution lnwallet.OutgoingHtlcResolution, checkpoints []checkpoint) { + t.Helper() + defer timeout()() // We first run the resolver from start to finish, ensuring it gets