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.

523 lines
12 KiB

  1. package loop
  2. import (
  3. "context"
  4. "testing"
  5. "github.com/btcsuite/btcd/wire"
  6. "github.com/btcsuite/btcutil"
  7. "github.com/lightninglabs/loop/loopdb"
  8. "github.com/lightninglabs/loop/swap"
  9. "github.com/lightninglabs/loop/test"
  10. "github.com/lightningnetwork/lnd/chainntnfs"
  11. "github.com/lightningnetwork/lnd/channeldb"
  12. "github.com/stretchr/testify/require"
  13. )
  14. var (
  15. testLoopInRequest = LoopInRequest{
  16. Amount: btcutil.Amount(50000),
  17. MaxSwapFee: btcutil.Amount(1000),
  18. HtlcConfTarget: 2,
  19. Initiator: "test",
  20. }
  21. )
  22. // TestLoopInSuccess tests the success scenario where the swap completes the
  23. // happy flow.
  24. func TestLoopInSuccess(t *testing.T) {
  25. defer test.Guard(t)()
  26. ctx := newLoopInTestContext(t)
  27. height := int32(600)
  28. cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
  29. initResult, err := newLoopInSwap(
  30. context.Background(), cfg,
  31. height, &testLoopInRequest,
  32. )
  33. if err != nil {
  34. t.Fatal(err)
  35. }
  36. swap := initResult.swap
  37. ctx.store.assertLoopInStored()
  38. errChan := make(chan error)
  39. go func() {
  40. err := swap.execute(context.Background(), ctx.cfg, height)
  41. if err != nil {
  42. log.Error(err)
  43. }
  44. errChan <- err
  45. }()
  46. ctx.assertState(loopdb.StateInitiated)
  47. ctx.assertState(loopdb.StateHtlcPublished)
  48. ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
  49. // Expect htlc to be published.
  50. htlcTx := <-ctx.lnd.SendOutputsChannel
  51. // We expect our cost to use the mock fee rate we set for our conf
  52. // target.
  53. cost := loopdb.SwapCost{
  54. Onchain: getTxFee(&htlcTx, test.DefaultMockFee.FeePerKVByte()),
  55. }
  56. // Expect the same state to be written again with the htlc tx hash
  57. // and on chain fee.
  58. state := ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
  59. require.NotNil(t, state.HtlcTxHash)
  60. require.Equal(t, cost, state.Cost)
  61. // Expect register for htlc conf.
  62. <-ctx.lnd.RegisterConfChannel
  63. <-ctx.lnd.RegisterConfChannel
  64. // Confirm htlc.
  65. ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
  66. Tx: &htlcTx,
  67. }
  68. // Client starts listening for spend of htlc.
  69. <-ctx.lnd.RegisterSpendChannel
  70. // Client starts listening for swap invoice updates.
  71. ctx.assertSubscribeInvoice(ctx.server.swapHash)
  72. // Server has already paid invoice before spending the htlc. Signal
  73. // settled.
  74. ctx.updateInvoiceState(49000, channeldb.ContractSettled)
  75. // Swap is expected to move to the state InvoiceSettled
  76. ctx.assertState(loopdb.StateInvoiceSettled)
  77. ctx.store.assertLoopInState(loopdb.StateInvoiceSettled)
  78. // Server spends htlc.
  79. successTx := wire.MsgTx{}
  80. successTx.AddTxIn(&wire.TxIn{
  81. Witness: [][]byte{{}, {}, {}},
  82. })
  83. ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
  84. SpendingTx: &successTx,
  85. SpenderInputIndex: 0,
  86. }
  87. ctx.assertState(loopdb.StateSuccess)
  88. ctx.store.assertLoopInState(loopdb.StateSuccess)
  89. err = <-errChan
  90. if err != nil {
  91. t.Fatal(err)
  92. }
  93. }
  94. // TestLoopInTimeout tests scenarios where the server doesn't sweep the htlc
  95. // and the client is forced to reclaim the funds using the timeout tx.
  96. func TestLoopInTimeout(t *testing.T) {
  97. testAmt := int64(testLoopInRequest.Amount)
  98. t.Run("internal htlc", func(t *testing.T) {
  99. testLoopInTimeout(t, swap.HtlcP2WSH, 0)
  100. })
  101. outputTypes := []swap.HtlcOutputType{swap.HtlcP2WSH, swap.HtlcNP2WSH}
  102. for _, outputType := range outputTypes {
  103. outputType := outputType
  104. t.Run(outputType.String(), func(t *testing.T) {
  105. t.Run("external htlc", func(t *testing.T) {
  106. testLoopInTimeout(t, outputType, testAmt)
  107. })
  108. t.Run("external amount too high", func(t *testing.T) {
  109. testLoopInTimeout(t, outputType, testAmt+1)
  110. })
  111. t.Run("external amount too low", func(t *testing.T) {
  112. testLoopInTimeout(t, outputType, testAmt-1)
  113. })
  114. })
  115. }
  116. }
  117. func testLoopInTimeout(t *testing.T,
  118. outputType swap.HtlcOutputType, externalValue int64) {
  119. defer test.Guard(t)()
  120. ctx := newLoopInTestContext(t)
  121. height := int32(600)
  122. cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
  123. req := testLoopInRequest
  124. if externalValue != 0 {
  125. req.ExternalHtlc = true
  126. }
  127. initResult, err := newLoopInSwap(
  128. context.Background(), cfg,
  129. height, &req,
  130. )
  131. if err != nil {
  132. t.Fatal(err)
  133. }
  134. s := initResult.swap
  135. ctx.store.assertLoopInStored()
  136. errChan := make(chan error)
  137. go func() {
  138. err := s.execute(context.Background(), ctx.cfg, height)
  139. if err != nil {
  140. log.Error(err)
  141. }
  142. errChan <- err
  143. }()
  144. ctx.assertState(loopdb.StateInitiated)
  145. ctx.assertState(loopdb.StateHtlcPublished)
  146. ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
  147. var (
  148. htlcTx wire.MsgTx
  149. cost loopdb.SwapCost
  150. )
  151. if externalValue == 0 {
  152. // Expect htlc to be published.
  153. htlcTx = <-ctx.lnd.SendOutputsChannel
  154. cost = loopdb.SwapCost{
  155. Onchain: getTxFee(
  156. &htlcTx, test.DefaultMockFee.FeePerKVByte(),
  157. ),
  158. }
  159. // Expect the same state to be written again with the htlc tx
  160. // hash and cost.
  161. state := ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
  162. require.NotNil(t, state.HtlcTxHash)
  163. require.Equal(t, cost, state.Cost)
  164. } else {
  165. // Create an external htlc publish tx.
  166. var pkScript []byte
  167. if outputType == swap.HtlcNP2WSH {
  168. pkScript = s.htlcNP2WSH.PkScript
  169. } else {
  170. pkScript = s.htlcP2WSH.PkScript
  171. }
  172. htlcTx = wire.MsgTx{
  173. TxOut: []*wire.TxOut{
  174. {
  175. PkScript: pkScript,
  176. Value: externalValue,
  177. },
  178. },
  179. }
  180. }
  181. // Expect register for htlc conf.
  182. <-ctx.lnd.RegisterConfChannel
  183. <-ctx.lnd.RegisterConfChannel
  184. // Confirm htlc.
  185. ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
  186. Tx: &htlcTx,
  187. }
  188. // Assert that the swap is failed in case of an invalid amount.
  189. invalidAmt := externalValue != 0 && externalValue != int64(req.Amount)
  190. if invalidAmt {
  191. ctx.assertState(loopdb.StateFailIncorrectHtlcAmt)
  192. ctx.store.assertLoopInState(loopdb.StateFailIncorrectHtlcAmt)
  193. err = <-errChan
  194. if err != nil {
  195. t.Fatal(err)
  196. }
  197. return
  198. }
  199. // Client starts listening for spend of htlc.
  200. <-ctx.lnd.RegisterSpendChannel
  201. // Client starts listening for swap invoice updates.
  202. ctx.assertSubscribeInvoice(ctx.server.swapHash)
  203. // Let htlc expire.
  204. ctx.blockEpochChan <- s.LoopInContract.CltvExpiry
  205. // Expect a signing request for the htlc tx output value.
  206. signReq := <-ctx.lnd.SignOutputRawChannel
  207. if signReq.SignDescriptors[0].Output.Value != htlcTx.TxOut[0].Value {
  208. t.Fatal("invalid signing amount")
  209. }
  210. // Expect timeout tx to be published.
  211. timeoutTx := <-ctx.lnd.TxPublishChannel
  212. // We can just get our sweep fee as we would in the swap code because
  213. // our estimate is static.
  214. fee, err := s.sweeper.GetSweepFee(
  215. context.Background(), s.htlc.AddTimeoutToEstimator,
  216. s.timeoutAddr, TimeoutTxConfTarget,
  217. )
  218. require.NoError(t, err)
  219. cost.Onchain += fee
  220. // Confirm timeout tx.
  221. ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
  222. SpendingTx: timeoutTx,
  223. SpenderInputIndex: 0,
  224. }
  225. // Now that timeout tx has confirmed, the client should be able to
  226. // safely cancel the swap invoice.
  227. <-ctx.lnd.FailInvoiceChannel
  228. // Signal that the invoice was canceled.
  229. ctx.updateInvoiceState(0, channeldb.ContractCanceled)
  230. ctx.assertState(loopdb.StateFailTimeout)
  231. state := ctx.store.assertLoopInState(loopdb.StateFailTimeout)
  232. require.Equal(t, cost, state.Cost)
  233. err = <-errChan
  234. if err != nil {
  235. t.Fatal(err)
  236. }
  237. }
  238. // TestLoopInResume tests resuming swaps in various states.
  239. func TestLoopInResume(t *testing.T) {
  240. storedVersion := []loopdb.ProtocolVersion{
  241. loopdb.ProtocolVersionUnrecorded,
  242. loopdb.ProtocolVersionHtlcV2,
  243. }
  244. htlcVersion := []swap.ScriptVersion{
  245. swap.HtlcV1,
  246. swap.HtlcV2,
  247. }
  248. for i, version := range storedVersion {
  249. version := version
  250. scriptVersion := htlcVersion[i]
  251. t.Run(version.String(), func(t *testing.T) {
  252. t.Run("initiated", func(t *testing.T) {
  253. testLoopInResume(
  254. t, loopdb.StateInitiated, false,
  255. version, scriptVersion,
  256. )
  257. })
  258. t.Run("initiated expired", func(t *testing.T) {
  259. testLoopInResume(
  260. t, loopdb.StateInitiated, true,
  261. version, scriptVersion,
  262. )
  263. })
  264. t.Run("htlc published", func(t *testing.T) {
  265. testLoopInResume(
  266. t, loopdb.StateHtlcPublished, false,
  267. version, scriptVersion,
  268. )
  269. })
  270. })
  271. }
  272. }
  273. func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
  274. storedVersion loopdb.ProtocolVersion, scriptVersion swap.ScriptVersion) {
  275. defer test.Guard(t)()
  276. ctx := newLoopInTestContext(t)
  277. cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
  278. senderKey := [33]byte{4}
  279. receiverKey := [33]byte{5}
  280. contract := &loopdb.LoopInContract{
  281. HtlcConfTarget: 2,
  282. SwapContract: loopdb.SwapContract{
  283. Preimage: testPreimage,
  284. AmountRequested: 100000,
  285. CltvExpiry: 744,
  286. ReceiverKey: receiverKey,
  287. SenderKey: senderKey,
  288. MaxSwapFee: 60000,
  289. MaxMinerFee: 50000,
  290. ProtocolVersion: storedVersion,
  291. },
  292. }
  293. pendSwap := &loopdb.LoopIn{
  294. Contract: contract,
  295. Loop: loopdb.Loop{
  296. Events: []*loopdb.LoopEvent{
  297. {
  298. SwapStateData: loopdb.SwapStateData{
  299. State: state,
  300. },
  301. },
  302. },
  303. Hash: testPreimage.Hash(),
  304. },
  305. }
  306. // If we have already published the htlc, we expect our cost to already
  307. // be published.
  308. var cost loopdb.SwapCost
  309. if state == loopdb.StateHtlcPublished {
  310. cost = loopdb.SwapCost{
  311. Onchain: 999,
  312. }
  313. pendSwap.Loop.Events[0].Cost = cost
  314. }
  315. htlc, err := swap.NewHtlc(
  316. scriptVersion, contract.CltvExpiry, contract.SenderKey,
  317. contract.ReceiverKey, testPreimage.Hash(), swap.HtlcNP2WSH,
  318. cfg.lnd.ChainParams,
  319. )
  320. if err != nil {
  321. t.Fatal(err)
  322. }
  323. err = ctx.store.CreateLoopIn(testPreimage.Hash(), contract)
  324. if err != nil {
  325. t.Fatal(err)
  326. }
  327. swap, err := resumeLoopInSwap(
  328. context.Background(), cfg,
  329. pendSwap,
  330. )
  331. if err != nil {
  332. t.Fatal(err)
  333. }
  334. var height int32
  335. if expired {
  336. height = 740
  337. } else {
  338. height = 600
  339. }
  340. errChan := make(chan error)
  341. go func() {
  342. err := swap.execute(context.Background(), ctx.cfg, height)
  343. if err != nil {
  344. log.Error(err)
  345. }
  346. errChan <- err
  347. }()
  348. defer func() {
  349. err = <-errChan
  350. if err != nil {
  351. t.Fatal(err)
  352. }
  353. select {
  354. case <-ctx.lnd.SendPaymentChannel:
  355. t.Fatal("unexpected payment sent")
  356. default:
  357. }
  358. select {
  359. case <-ctx.lnd.SendOutputsChannel:
  360. t.Fatal("unexpected tx published")
  361. default:
  362. }
  363. }()
  364. var htlcTx wire.MsgTx
  365. if state == loopdb.StateInitiated {
  366. ctx.assertState(loopdb.StateInitiated)
  367. if expired {
  368. ctx.assertState(loopdb.StateFailTimeout)
  369. return
  370. }
  371. ctx.assertState(loopdb.StateHtlcPublished)
  372. ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
  373. // Expect htlc to be published.
  374. htlcTx = <-ctx.lnd.SendOutputsChannel
  375. cost = loopdb.SwapCost{
  376. Onchain: getTxFee(
  377. &htlcTx, test.DefaultMockFee.FeePerKVByte(),
  378. ),
  379. }
  380. // Expect the same state to be written again with the htlc tx
  381. // hash.
  382. state := ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
  383. require.NotNil(t, state.HtlcTxHash)
  384. } else {
  385. ctx.assertState(loopdb.StateHtlcPublished)
  386. htlcTx.AddTxOut(&wire.TxOut{
  387. PkScript: htlc.PkScript,
  388. Value: int64(contract.AmountRequested),
  389. })
  390. }
  391. // Expect register for htlc conf.
  392. <-ctx.lnd.RegisterConfChannel
  393. <-ctx.lnd.RegisterConfChannel
  394. // Confirm htlc.
  395. ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
  396. Tx: &htlcTx,
  397. }
  398. // Client starts listening for spend of htlc.
  399. <-ctx.lnd.RegisterSpendChannel
  400. // Client starts listening for swap invoice updates.
  401. ctx.assertSubscribeInvoice(testPreimage.Hash())
  402. // Server has already paid invoice before spending the htlc. Signal
  403. // settled.
  404. amtPaid := btcutil.Amount(49000)
  405. ctx.updateInvoiceState(amtPaid, channeldb.ContractSettled)
  406. // Swap is expected to move to the state InvoiceSettled
  407. ctx.assertState(loopdb.StateInvoiceSettled)
  408. ctx.store.assertLoopInState(loopdb.StateInvoiceSettled)
  409. // Server spends htlc.
  410. successTx := wire.MsgTx{}
  411. successTx.AddTxIn(&wire.TxIn{
  412. Witness: [][]byte{{}, {}, {}},
  413. })
  414. successTxHash := successTx.TxHash()
  415. ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
  416. SpendingTx: &successTx,
  417. SpenderTxHash: &successTxHash,
  418. SpenderInputIndex: 0,
  419. }
  420. ctx.assertState(loopdb.StateSuccess)
  421. finalState := ctx.store.assertLoopInState(loopdb.StateSuccess)
  422. // We expect our server fee to reflect as the difference between htlc
  423. // value and invoice amount paid. We use our original on-chain cost, set
  424. // earlier in the test, because we expect this value to be unchanged.
  425. cost.Server = btcutil.Amount(htlcTx.TxOut[0].Value) - amtPaid
  426. require.Equal(t, cost, finalState.Cost)
  427. }