diff --git a/loopout_test.go b/loopout_test.go index 0e72933..cf6974d 100644 --- a/loopout_test.go +++ b/loopout_test.go @@ -593,3 +593,132 @@ func TestPreimagePush(t *testing.T) { require.NoError(t, <-errChan) } + +// TestNotPublishPreimageAfterTimeout validates that when the on-chain HTLC has +// timed out, the client won't sweep the HTLC anymore by publishing the +// preimage. +func TestNotPublishPreimageAfterTimeout(t *testing.T) { + defer test.Guard(t)() + + lnd := test.NewMockLnd() + ctx := test.NewContext(t, lnd) + server := newServerMock(lnd) + + testReq := *testRequest + + // Set on-chain HTLC CLTV. Note that the invoice would have an extra + // on-off chain cltv delta. + testReq.Expiry = ctx.Lnd.Height + testLoopOutMinOnChainCltvDelta + + // We set our mock fee estimate for our target sweep confs to be our + // max miner fee *2, so that our fee will definitely be above what we + // are willing to pay, and we will not sweep. + testReq.SweepConfTarget = testLoopOutMinOnChainCltvDelta - + DefaultSweepConfTargetDelta - 1 + ctx.Lnd.SetFeeEstimate( + testReq.SweepConfTarget, chainfee.SatPerKWeight( + testReq.MaxMinerFee*2, + ), + ) + + // We set the fee estimate for our default confirmation target very + // low, so that once we drop down to our default confs we will start + // trying to sweep the preimage. + ctx.Lnd.SetFeeEstimate(DefaultSweepConfTarget, 1) + + // Setup the cfg using mock server and init a loop out request. + cfg := newSwapConfig( + &lnd.LndServices, newStoreMock(t), server, + ) + initResult, err := newLoopOutSwap( + context.Background(), cfg, ctx.Lnd.Height, &testReq, + ) + require.NoError(t, err) + swap := initResult.swap + + // Set up the required dependencies to execute the swap. + sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices} + blockEpochChan := make(chan interface{}) + statusChan := make(chan SwapInfo) + expiryChan := make(chan time.Time) + timerFactory := func(_ time.Duration) <-chan time.Time { + return expiryChan + } + + errChan := make(chan error) + go func() { + err := swap.execute(context.Background(), &executeConfig{ + statusChan: statusChan, + blockEpochChan: blockEpochChan, + timerFactory: timerFactory, + sweeper: sweeper, + }, ctx.Lnd.Height) + if err != nil { + log.Error(err) + } + errChan <- err + }() + + // The swap should be found in its initial state. + cfg.store.(*storeMock).assertLoopOutStored() + state := <-statusChan + require.Equal(t, loopdb.StateInitiated, state.State) + + // We'll then pay both the swap and prepay invoice, which should trigger + // the server to publish the on-chain HTLC. + signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc) + signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) + + signalSwapPaymentResult(nil) + signalPrepaymentResult(nil) + + // Notify the confirmation notification for the HTLC. + ctx.AssertRegisterConf(false, defaultConfirmations) + + // Advance the block height to get the HTLC confirmed. + blockEpochChan <- ctx.Lnd.Height + 1 + + htlcTx := wire.NewMsgTx(2) + htlcTx.AddTxOut(&wire.TxOut{ + Value: int64(swap.AmountRequested), + PkScript: swap.htlc.PkScript, + }) + ctx.NotifyConf(htlcTx) + + // The client should then register for a spend of the HTLC and attempt + // to sweep it using the custom confirmation target. + ctx.AssertRegisterSpendNtfn(swap.htlc.PkScript) + + // Assert that we made a query to track our payment, as required for + // preimage push tracking. + ctx.AssertTrackPayment() + + // Tick the expiry channel, we are still using our client confirmation + // target at this stage which has fees higher than our max acceptable + // fee. We do not expect a sweep attempt at this point. Since our + // preimage is not revealed, we also do not expect a preimage push. + expiryChan <- testTime + + // Now, we notify the height at which the client will start using the + // default confirmation target. This has the effect of lowering our fees + // so that the client still start sweeping. Also note that height we + // are using here means the on-chain HTLC has expired. + blockEpochChan <- testReq.Expiry + 1 + + // This time when we tick the expiry chan, our fees are lower than the + // swap max, however because the on-chain HTLC has expired, we should + // abort the sweep and mark it as failed. + expiryChan <- testTime + + // Expect a signing request for the HTLC success transaction. + <-ctx.Lnd.SignOutputRawChannel + + // We should see our swap marked as failed. + cfg.store.(*storeMock).assertLoopOutState(loopdb.StateFailSweepTimeout) + status := <-statusChan + require.Equal( + t, status.State, loopdb.StateFailSweepTimeout, + ) + + require.NoError(t, <-errChan) +}