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.

263 lines
5.5 KiB

  1. package loop
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/btcsuite/btcd/wire"
  7. "github.com/btcsuite/btcutil"
  8. "github.com/lightninglabs/lndclient"
  9. "github.com/lightninglabs/loop/loopdb"
  10. "github.com/lightninglabs/loop/swap"
  11. "github.com/lightninglabs/loop/sweep"
  12. "github.com/lightninglabs/loop/test"
  13. "github.com/lightningnetwork/lnd/chainntnfs"
  14. "github.com/lightningnetwork/lnd/lnrpc"
  15. "github.com/lightningnetwork/lnd/lntypes"
  16. "github.com/stretchr/testify/require"
  17. )
  18. var (
  19. testPreimage = lntypes.Preimage([32]byte{
  20. 1, 1, 1, 1, 2, 2, 2, 2,
  21. 3, 3, 3, 3, 4, 4, 4, 4,
  22. 1, 1, 1, 1, 2, 2, 2, 2,
  23. 3, 3, 3, 3, 4, 4, 4, 4,
  24. })
  25. )
  26. // testContext contains functionality to support client unit tests.
  27. type testContext struct {
  28. test.Context
  29. serverMock *serverMock
  30. swapClient *Client
  31. statusChan chan SwapInfo
  32. store *storeMock
  33. expiryChan chan time.Time
  34. runErr chan error
  35. stop func()
  36. }
  37. func newSwapClient(config *clientConfig) *Client {
  38. sweeper := &sweep.Sweeper{
  39. Lnd: config.LndServices,
  40. }
  41. lndServices := config.LndServices
  42. executor := newExecutor(&executorConfig{
  43. lnd: lndServices,
  44. store: config.Store,
  45. sweeper: sweeper,
  46. createExpiryTimer: config.CreateExpiryTimer,
  47. cancelSwap: config.Server.CancelLoopOutSwap,
  48. })
  49. return &Client{
  50. errChan: make(chan error),
  51. clientConfig: *config,
  52. lndServices: lndServices,
  53. sweeper: sweeper,
  54. executor: executor,
  55. resumeReady: make(chan struct{}),
  56. }
  57. }
  58. func createClientTestContext(t *testing.T,
  59. pendingSwaps []*loopdb.LoopOut) *testContext {
  60. clientLnd := test.NewMockLnd()
  61. serverMock := newServerMock(clientLnd)
  62. store := newStoreMock(t)
  63. for _, s := range pendingSwaps {
  64. store.loopOutSwaps[s.Hash] = s.Contract
  65. updates := []loopdb.SwapStateData{}
  66. for _, e := range s.Events {
  67. updates = append(updates, e.SwapStateData)
  68. }
  69. store.loopOutUpdates[s.Hash] = updates
  70. }
  71. expiryChan := make(chan time.Time)
  72. timerFactory := func(expiry time.Duration) <-chan time.Time {
  73. return expiryChan
  74. }
  75. swapClient := newSwapClient(&clientConfig{
  76. LndServices: &clientLnd.LndServices,
  77. Server: serverMock,
  78. Store: store,
  79. CreateExpiryTimer: timerFactory,
  80. })
  81. statusChan := make(chan SwapInfo)
  82. ctx := &testContext{
  83. Context: test.NewContext(
  84. t,
  85. clientLnd,
  86. ),
  87. swapClient: swapClient,
  88. statusChan: statusChan,
  89. expiryChan: expiryChan,
  90. store: store,
  91. serverMock: serverMock,
  92. }
  93. ctx.runErr = make(chan error)
  94. runCtx, stop := context.WithCancel(context.Background())
  95. ctx.stop = stop
  96. go func() {
  97. err := swapClient.Run(
  98. runCtx,
  99. statusChan,
  100. )
  101. log.Errorf("client run: %v", err)
  102. ctx.runErr <- err
  103. }()
  104. return ctx
  105. }
  106. func (ctx *testContext) finish() {
  107. ctx.stop()
  108. select {
  109. case err := <-ctx.runErr:
  110. if err != nil {
  111. ctx.T.Fatal(err)
  112. }
  113. case <-time.After(test.Timeout):
  114. ctx.T.Fatal("client not stopping")
  115. }
  116. ctx.assertIsDone()
  117. }
  118. // notifyHeight notifies swap client of the arrival of a new block and
  119. // waits for the notification to be processed by selecting on a dedicated
  120. // test channel.
  121. func (ctx *testContext) notifyHeight(height int32) {
  122. ctx.T.Helper()
  123. if err := ctx.Lnd.NotifyHeight(height); err != nil {
  124. ctx.T.Fatal(err)
  125. }
  126. }
  127. func (ctx *testContext) assertIsDone() {
  128. if err := ctx.Lnd.IsDone(); err != nil {
  129. ctx.T.Fatal(err)
  130. }
  131. if err := ctx.store.isDone(); err != nil {
  132. ctx.T.Fatal(err)
  133. }
  134. select {
  135. case <-ctx.statusChan:
  136. ctx.T.Fatalf("not all status updates read")
  137. default:
  138. }
  139. }
  140. func (ctx *testContext) assertStored() {
  141. ctx.T.Helper()
  142. ctx.store.assertLoopOutStored()
  143. }
  144. func (ctx *testContext) assertStorePreimageReveal() {
  145. ctx.T.Helper()
  146. ctx.store.assertStorePreimageReveal()
  147. }
  148. func (ctx *testContext) assertStoreFinished(expectedResult loopdb.SwapState) {
  149. ctx.T.Helper()
  150. ctx.store.assertStoreFinished(expectedResult)
  151. }
  152. func (ctx *testContext) assertStatus(expectedState loopdb.SwapState) {
  153. ctx.T.Helper()
  154. for {
  155. select {
  156. case update := <-ctx.statusChan:
  157. if update.SwapType != swap.TypeOut {
  158. continue
  159. }
  160. if update.State == expectedState {
  161. return
  162. }
  163. case <-time.After(test.Timeout):
  164. ctx.T.Fatalf("expected status %v not "+
  165. "received in time", expectedState)
  166. }
  167. }
  168. }
  169. func (ctx *testContext) publishHtlc(script []byte,
  170. amt btcutil.Amount) wire.OutPoint {
  171. // Create the htlc tx.
  172. htlcTx := wire.MsgTx{}
  173. htlcTx.AddTxIn(&wire.TxIn{
  174. PreviousOutPoint: wire.OutPoint{},
  175. })
  176. htlcTx.AddTxOut(&wire.TxOut{
  177. PkScript: script,
  178. Value: int64(amt),
  179. })
  180. htlcTxHash := htlcTx.TxHash()
  181. // Signal client that script has been published.
  182. select {
  183. case ctx.Lnd.ConfChannel <- &chainntnfs.TxConfirmation{
  184. Tx: &htlcTx,
  185. }:
  186. case <-time.After(test.Timeout):
  187. ctx.T.Fatalf("htlc confirmed not consumed")
  188. }
  189. return wire.OutPoint{
  190. Hash: htlcTxHash,
  191. Index: 0,
  192. }
  193. }
  194. // trackPayment asserts that a call to track payment was sent and sends the
  195. // status provided into the updates channel.
  196. func (ctx *testContext) trackPayment(status lnrpc.Payment_PaymentStatus) {
  197. trackPayment := ctx.Context.AssertTrackPayment()
  198. select {
  199. case trackPayment.Updates <- lndclient.PaymentStatus{
  200. State: status,
  201. }:
  202. case <-time.After(test.Timeout):
  203. ctx.T.Fatalf("could not send payment update")
  204. }
  205. }
  206. // assertPreimagePush asserts that we made an attempt to push our preimage to
  207. // the server.
  208. func (ctx *testContext) assertPreimagePush(preimage lntypes.Preimage) {
  209. select {
  210. case pushedPreimage := <-ctx.serverMock.preimagePush:
  211. require.Equal(ctx.T, preimage, pushedPreimage)
  212. case <-time.After(test.Timeout):
  213. ctx.T.Fatalf("preimage not pushed")
  214. }
  215. }