You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
6.9 KiB

  1. package loop
  2. import (
  3. "context"
  4. "errors"
  5. "testing"
  6. "time"
  7. "github.com/btcsuite/btcd/blockchain"
  8. "github.com/btcsuite/btcd/wire"
  9. "github.com/btcsuite/btcutil"
  10. "github.com/lightninglabs/loop/lndclient"
  11. "github.com/lightninglabs/loop/loopdb"
  12. "github.com/lightninglabs/loop/sweep"
  13. "github.com/lightninglabs/loop/test"
  14. )
  15. // TestLateHtlcPublish tests that the client is not revealing the preimage if
  16. // there are not enough blocks left.
  17. func TestLateHtlcPublish(t *testing.T) {
  18. defer test.Guard(t)()
  19. lnd := test.NewMockLnd()
  20. ctx := test.NewContext(t, lnd)
  21. server := newServerMock()
  22. store := newStoreMock(t)
  23. expiryChan := make(chan time.Time)
  24. timerFactory := func(expiry time.Duration) <-chan time.Time {
  25. return expiryChan
  26. }
  27. height := int32(600)
  28. cfg := &swapConfig{
  29. lnd: &lnd.LndServices,
  30. store: store,
  31. server: server,
  32. }
  33. swap, err := newLoopOutSwap(
  34. context.Background(), cfg, height, testRequest,
  35. )
  36. if err != nil {
  37. t.Fatal(err)
  38. }
  39. sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices}
  40. blockEpochChan := make(chan interface{})
  41. statusChan := make(chan SwapInfo)
  42. errChan := make(chan error)
  43. go func() {
  44. err := swap.execute(context.Background(), &executeConfig{
  45. statusChan: statusChan,
  46. sweeper: sweeper,
  47. blockEpochChan: blockEpochChan,
  48. timerFactory: timerFactory,
  49. }, height)
  50. if err != nil {
  51. log.Error(err)
  52. }
  53. errChan <- err
  54. }()
  55. store.assertLoopOutStored()
  56. state := <-statusChan
  57. if state.State != loopdb.StateInitiated {
  58. t.Fatal("unexpected state")
  59. }
  60. signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
  61. signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
  62. // Expect client to register for conf
  63. ctx.AssertRegisterConf()
  64. // // Wait too long before publishing htlc.
  65. blockEpochChan <- int32(swap.CltvExpiry - 10)
  66. signalSwapPaymentResult(
  67. errors.New(lndclient.PaymentResultUnknownPaymentHash),
  68. )
  69. signalPrepaymentResult(
  70. errors.New(lndclient.PaymentResultUnknownPaymentHash),
  71. )
  72. store.assertStoreFinished(loopdb.StateFailTimeout)
  73. status := <-statusChan
  74. if status.State != loopdb.StateFailTimeout {
  75. t.Fatal("unexpected state")
  76. }
  77. err = <-errChan
  78. if err != nil {
  79. t.Fatal(err)
  80. }
  81. }
  82. // TestCustomSweepConfTarget ensures we are able to sweep a Loop Out HTLC with a
  83. // custom confirmation target.
  84. func TestCustomSweepConfTarget(t *testing.T) {
  85. defer test.Guard(t)()
  86. lnd := test.NewMockLnd()
  87. ctx := test.NewContext(t, lnd)
  88. // Use the highest sweep confirmation target before we attempt to use
  89. // the default.
  90. testRequest.SweepConfTarget = testLoopOutOnChainCltvDelta -
  91. DefaultSweepConfTargetDelta - 1
  92. // Set up custom fee estimates such that the lower confirmation target
  93. // yields a much higher fee rate.
  94. ctx.Lnd.SetFeeEstimate(testRequest.SweepConfTarget, 250)
  95. ctx.Lnd.SetFeeEstimate(DefaultSweepConfTarget, 10000)
  96. cfg := &swapConfig{
  97. lnd: &lnd.LndServices,
  98. store: newStoreMock(t),
  99. server: newServerMock(),
  100. }
  101. swap, err := newLoopOutSwap(
  102. context.Background(), cfg, ctx.Lnd.Height, testRequest,
  103. )
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. // Set up the required dependencies to execute the swap.
  108. //
  109. // TODO: create test context similar to loopInTestContext.
  110. sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices}
  111. blockEpochChan := make(chan interface{})
  112. statusChan := make(chan SwapInfo)
  113. expiryChan := make(chan time.Time)
  114. timerFactory := func(expiry time.Duration) <-chan time.Time {
  115. return expiryChan
  116. }
  117. errChan := make(chan error)
  118. go func() {
  119. err := swap.execute(context.Background(), &executeConfig{
  120. statusChan: statusChan,
  121. blockEpochChan: blockEpochChan,
  122. timerFactory: timerFactory,
  123. sweeper: sweeper,
  124. }, ctx.Lnd.Height)
  125. if err != nil {
  126. log.Error(err)
  127. }
  128. errChan <- err
  129. }()
  130. // The swap should be found in its initial state.
  131. cfg.store.(*storeMock).assertLoopOutStored()
  132. state := <-statusChan
  133. if state.State != loopdb.StateInitiated {
  134. t.Fatal("unexpected state")
  135. }
  136. // We'll then pay both the swap and prepay invoice, which should trigger
  137. // the server to publish the on-chain HTLC.
  138. signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
  139. signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
  140. signalSwapPaymentResult(nil)
  141. signalPrepaymentResult(nil)
  142. // Notify the confirmation notification for the HTLC.
  143. ctx.AssertRegisterConf()
  144. blockEpochChan <- ctx.Lnd.Height + 1
  145. htlcTx := wire.NewMsgTx(2)
  146. htlcTx.AddTxOut(&wire.TxOut{
  147. Value: int64(swap.AmountRequested),
  148. PkScript: swap.htlc.PkScript,
  149. })
  150. ctx.NotifyConf(htlcTx)
  151. // The client should then register for a spend of the HTLC and attempt
  152. // to sweep it using the custom confirmation target.
  153. ctx.AssertRegisterSpendNtfn(swap.htlc.PkScript)
  154. expiryChan <- time.Now()
  155. cfg.store.(*storeMock).assertLoopOutState(loopdb.StatePreimageRevealed)
  156. status := <-statusChan
  157. if status.State != loopdb.StatePreimageRevealed {
  158. t.Fatalf("expected state %v, got %v",
  159. loopdb.StatePreimageRevealed, status.State)
  160. }
  161. // assertSweepTx performs some sanity checks on a sweep transaction to
  162. // ensure it was constructed correctly.
  163. assertSweepTx := func(expConfTarget int32) *wire.MsgTx {
  164. t.Helper()
  165. sweepTx := ctx.ReceiveTx()
  166. if sweepTx.TxIn[0].PreviousOutPoint.Hash != htlcTx.TxHash() {
  167. t.Fatalf("expected sweep tx to spend %v, got %v",
  168. htlcTx.TxHash(), sweepTx.TxIn[0].PreviousOutPoint)
  169. }
  170. // The fee used for the sweep transaction is an estimate based
  171. // on the maximum witness size, so we should expect to see a
  172. // lower fee when using the actual witness size of the
  173. // transaction.
  174. fee := btcutil.Amount(
  175. htlcTx.TxOut[0].Value - sweepTx.TxOut[0].Value,
  176. )
  177. weight := blockchain.GetTransactionWeight(btcutil.NewTx(sweepTx))
  178. feeRate, err := ctx.Lnd.WalletKit.EstimateFee(
  179. context.Background(), expConfTarget,
  180. )
  181. if err != nil {
  182. t.Fatalf("unable to retrieve fee estimate: %v", err)
  183. }
  184. minFee := feeRate.FeeForWeight(weight)
  185. maxFee := btcutil.Amount(float64(minFee) * 1.1)
  186. if fee < minFee && fee > maxFee {
  187. t.Fatalf("expected sweep tx to have fee between %v-%v, "+
  188. "got %v", minFee, maxFee, fee)
  189. }
  190. return sweepTx
  191. }
  192. // The sweep should have a fee that corresponds to the custom
  193. // confirmation target.
  194. _ = assertSweepTx(testRequest.SweepConfTarget)
  195. // We'll then notify the height at which we begin using the default
  196. // confirmation target.
  197. defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
  198. DefaultSweepConfTargetDelta
  199. blockEpochChan <- int32(defaultConfTargetHeight)
  200. expiryChan <- time.Now()
  201. // We should expect to see another sweep using the higher fee since the
  202. // spend hasn't been confirmed yet.
  203. sweepTx := assertSweepTx(DefaultSweepConfTarget)
  204. // Notify the spend so that the swap reaches its final state.
  205. ctx.NotifySpend(sweepTx, 0)
  206. cfg.store.(*storeMock).assertLoopOutState(loopdb.StateSuccess)
  207. status = <-statusChan
  208. if status.State != loopdb.StateSuccess {
  209. t.Fatalf("expected state %v, got %v", loopdb.StateSuccess,
  210. status.State)
  211. }
  212. if err := <-errChan; err != nil {
  213. t.Fatal(err)
  214. }
  215. }