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.

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