packages feed

lnd-client-0.1.0.0: test/LndClient/RPCSpec.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}

module LndClient.RPCSpec
  ( spec,
  )
where

import LndClient.Data.AddHodlInvoice as HodlInvoice (AddHodlInvoiceRequest (..))
import LndClient.Data.AddInvoice as AddInvoice
  ( AddInvoiceRequest (..),
    AddInvoiceResponse (..),
  )
import LndClient.Data.Channel as Channel (Channel (..))
import LndClient.Data.CloseChannel (CloseChannelRequest (..))
import qualified LndClient.Data.CloseChannel as CloseChannel
import qualified LndClient.Data.ClosedChannels as ClosedChannels
import LndClient.Data.GetInfo (GetInfoResponse (..))
import LndClient.Data.Invoice as Invoice (Invoice (..), InvoiceState (..))
import LndClient.Data.ListChannels as LC (ListChannelsRequest (..))
import LndClient.Data.ListInvoices as ListInvoices
  ( ListInvoiceRequest (..),
    ListInvoiceResponse (..),
  )
import LndClient.Data.OpenChannel (OpenChannelRequest (..))
import LndClient.Data.PayReq as PayReq (PayReq (..))
import LndClient.Data.SendPayment (SendPaymentRequest (..))
import LndClient.Data.SubscribeInvoices
  ( SubscribeInvoicesRequest (..),
  )
import LndClient.Import
import LndClient.LndTest
import LndClient.QRCode
import LndClient.RPC.Katip
import LndClient.TestApp
import qualified LndClient.Watcher as Watcher
import Test.Hspec

spec :: Spec
spec = do
  it "fromJSON" $ do
    x <- readLndEnv
    envLndSyncGrpcTimeout x `shouldBe` newGrpcTimeout 59
    envLndAsyncGrpcTimeout x `shouldBe` Nothing
  it "decodePayReq" $ withEnv $ do
    lnd <- getLndEnv Bob
    x0 <- liftLndResult =<< addInvoice lnd addInvoiceRequest
    x1 <-
      liftLndResult
        =<< decodePayReq lnd (AddInvoice.paymentRequest x0)
    liftIO $ do
      PayReq.paymentHash x1
        `shouldBe` AddInvoice.rHash x0
      PayReq.numMsat x1
        `shouldBe` AddInvoice.valueMsat addInvoiceRequest
      Just (PayReq.expiry x1)
        `shouldBe` AddInvoice.expiry addInvoiceRequest
  it "lookupInvoice" $ withEnv $ do
    lnd <- getLndEnv Bob
    x0 <- liftLndResult =<< addInvoice lnd addInvoiceRequest
    x1 <- liftLndResult =<< lookupInvoice lnd (AddInvoice.rHash x0)
    liftIO $ do
      Invoice.rHash x1
        `shouldBe` AddInvoice.rHash x0
      Invoice.paymentRequest x1
        `shouldBe` AddInvoice.paymentRequest x0
      Invoice.addIndex x1
        `shouldBe` AddInvoice.addIndex x0
      Just (Invoice.memo x1)
        `shouldBe` AddInvoice.memo addInvoiceRequest
      Invoice.valueMsat x1
        `shouldBe` AddInvoice.valueMsat addInvoiceRequest
  it "addInvoice" $ withEnv $ do
    lnd <- getLndEnv Bob
    res <- addInvoice lnd addInvoiceRequest
    liftIO $ res `shouldSatisfy` isRight
  it "addInvoiceQrCode" $ withEnv $ do
    lnd <- getLndEnv Bob
    res <- liftLndResult =<< addInvoice lnd addInvoiceRequest
    let qr = qrPngDataUrl qrDefOpts (AddInvoice.paymentRequest res)
    liftIO $ qr `shouldSatisfy` isJust
  it "addNormalInvoice" $ withEnv $ do
    queue <- getInvoiceTChan Bob
    lnd <- getLndEnv Bob
    inv <- liftLndResult =<< addInvoice lnd addInvoiceRequest
    res <-
      receiveInvoice
        (AddInvoice.rHash inv)
        Invoice.OPEN
        queue
    liftIO $ res `shouldSatisfy` isRight
  it "settleNormalInvoice" $ withEnv $ do
    void $ setupOneChannel Alice Bob
    chan <- getInvoiceTChan Bob
    bob <- getLndEnv Bob
    inv <- liftLndResult =<< addInvoice bob addInvoiceRequest
    let rh = AddInvoice.rHash inv
    liftLndResult
      =<< receiveInvoice rh Invoice.OPEN chan
    alice <- getLndEnv Alice
    let pr = AddInvoice.paymentRequest inv
    let spr = SendPaymentRequest pr $ MSat 1000000
    void $ liftLndResult =<< sendPayment alice spr
    res <- receiveInvoice rh Invoice.SETTLED chan
    liftIO $ res `shouldSatisfy` isRight
  it "addHodlInvoice" $ withEnv $ do
    lnd <- getLndEnv Bob
    r <- newRPreimage
    let req =
          AddHodlInvoiceRequest
            { memo = Just "HELLO",
              hash = newRHash r,
              valueMsat = MSat 1000000,
              expiry = Just $ Seconds 1000
            }
    x0 <- addHodlInvoice lnd req
    x1 <- ensureHodlInvoice lnd req
    liftIO $ do
      x0 `shouldSatisfy` isRight
      x0 `shouldBe` AddInvoice.paymentRequest <$> x1
  it "watchNormal" $ withEnv $ do
    bob <- getLndEnv Bob
    w <- Watcher.spawnLink bob subscribeInvoicesChan ignore3
    chan <- Watcher.dupLndTChan w
    Watcher.watch w subscribeInvoicesRequest
    inv <- liftLndResult =<< addInvoice bob addInvoiceRequest
    res <-
      receiveInvoice
        (AddInvoice.rHash inv)
        Invoice.OPEN
        chan
    Watcher.delete w
    liftIO $ res `shouldSatisfy` isRight
  it "watchUnit" $ withEnv $ do
    bob <- getLndEnv Bob
    alice <- getLndEnv Alice
    w <- Watcher.spawnLinkUnit bob subscribeChannelEventsChan ignore2
    chan <- Watcher.dupLndTChan w
    Watcher.watchUnit w
    GetInfoResponse bobPubKey _ _ <- liftLndResult =<< getInfo bob
    let openChannelRequest =
          OpenChannelRequest
            { nodePubkey = bobPubKey,
              localFundingAmount = MSat 20000000,
              pushSat = Just $ MSat 1000000,
              targetConf = Nothing,
              satPerByte = Nothing,
              private = Nothing,
              minHtlcMsat = Nothing,
              remoteCsvDelay = Nothing,
              minConfs = Nothing,
              spendUnconfirmed = Nothing,
              closeAddress = Nothing
            }
    cp <- liftLndResult =<< openChannelSync alice openChannelRequest
    res <- receiveActiveChannel proxyOwner cp chan
    Watcher.delete w
    liftIO $ res `shouldSatisfy` isRight
  it "unWatch" $ withEnv $ do
    lnd <- getLndEnv Bob
    w <-
      Watcher.spawnLink
        lnd
        subscribeInvoicesChan
        -- can unWatch from the callback
        $ \w k _ -> Watcher.unWatch w k
    chan <- Watcher.dupLndTChan w
    Watcher.watch w subscribeInvoicesRequest
    void . liftLndResult =<< addInvoice lnd addInvoiceRequest
    -- can unWatch from the outside as well
    Watcher.unWatch w subscribeInvoicesRequest
    purgeChan chan
    void . liftLndResult =<< addInvoice lnd addInvoiceRequest
    res <- readTChanTimeout (MicroSecondsDelay 500000) chan
    Watcher.delete w
    liftIO $ res `shouldBe` Nothing
  it "ensureHodlInvoice" $ withEnv $ do
    r <- newRPreimage
    let req =
          AddHodlInvoiceRequest
            { memo = Just "HELLO",
              hash = newRHash r,
              valueMsat = MSat 1000000,
              expiry = Just $ Seconds 1000
            }
    bob <- getLndEnv Bob
    res <- ensureHodlInvoice bob req
    liftIO $ res `shouldSatisfy` isRight
  it "cancelInvoice" $ withEnv $ do
    void $ setupOneChannel Alice Bob
    r <- newRPreimage
    let rh = newRHash r
    let hipr =
          AddHodlInvoiceRequest
            Nothing
            rh
            (MSat 1000000)
            Nothing
    bob <- getLndEnv Bob
    q <- getInvoiceTChan Bob
    qq <- getSingleInvoiceTChan Bob
    pr <-
      liftLndResult
        =<< addHodlInvoice bob hipr
    watchSingleInvoice Bob rh
    liftLndResult
      =<< receiveInvoice rh Invoice.OPEN q
    let spr = SendPaymentRequest pr $ MSat 1000000
    alice <- getLndEnv Alice
    withSpawnLink
      (liftLndResult =<< sendPayment alice spr)
      ( const $ do
          --
          -- NOTE : this is working only in coingaming LND fork
          -- which is fixing this bug
          -- https://github.com/lightningnetwork/lnd/issues/4544
          --
          liftLndResult
            =<< receiveInvoice rh Invoice.ACCEPTED qq
          res <- cancelInvoice bob rh
          liftLndResult
            =<< receiveInvoice rh Invoice.CANCELED qq
          liftIO $ res `shouldSatisfy` isRight
      )
  it "settleInvoice" $ withEnv $ do
    void $ setupOneChannel Alice Bob
    r <- newRPreimage
    let rh = newRHash r
    let hipr =
          AddHodlInvoiceRequest Nothing rh (MSat 1000000) Nothing
    bob <- getLndEnv Bob
    q <- getInvoiceTChan Bob
    qq <- getSingleInvoiceTChan Bob
    pr <- liftLndResult =<< addHodlInvoice bob hipr
    watchSingleInvoice Bob rh
    liftLndResult
      =<< receiveInvoice rh Invoice.OPEN q
    let spr = SendPaymentRequest pr $ MSat 1000000
    alice <- getLndEnv Alice
    withSpawnLink
      (liftLndResult =<< sendPayment alice spr)
      ( const $ do
          --
          -- NOTE : this is working only in coingaming LND fork
          -- which is fixing this bug
          -- https://github.com/lightningnetwork/lnd/issues/4544
          --
          liftLndResult
            =<< receiveInvoice rh Invoice.ACCEPTED qq
          res <- settleInvoice bob r
          liftLndResult
            =<< receiveInvoice rh Invoice.SETTLED qq
          liftIO $ res `shouldSatisfy` isRight
      )
  it "listInvoices" $ withEnv $ do
    lnd <- getLndEnv Bob
    rh <-
      AddInvoice.rHash
        <$> (liftLndResult =<< addInvoice lnd addInvoiceRequest)
    let listReq =
          ListInvoiceRequest
            { pendingOnly = False,
              indexOffset = AddIndex 0,
              numMaxInvoices = 0,
              reversed = False
            }
    xs <-
      ListInvoices.invoices
        <$> (liftLndResult =<< listInvoices lnd listReq)
    liftIO $ xs `shouldSatisfy` any ((rh ==) . Invoice.rHash)
  it "listChannelAndClose" $ withEnv $ do
    void $ setupOneChannel Alice Bob
    lnd <- getLndEnv Bob
    let listReq = ListChannelsRequest False False False False Nothing
    cs0 <- liftLndResult =<< listChannels lnd listReq
    lazyConnectNodes proxyOwner
    cp <-
      liftMaybe "ChannelPoint is required" $
        Channel.channelPoint <$> safeHead cs0
    withSpawnLink
      ( closeChannel
          (const $ return ())
          lnd
          (CloseChannelRequest cp False Nothing Nothing Nothing)
      )
      ( const $ do
          liftLndResult =<< receiveClosedChannels proxyOwner [cp]
          cs1 <- liftLndResult =<< listChannels lnd listReq
          liftIO $ (length cs0 - length cs1) `shouldBe` 1
      )
  it "closedChannels" $ withEnv $ do
    cp <- setupOneChannel Alice Bob
    closeAllChannels proxyOwner
    lnd <- getLndEnv Bob
    res <- closedChannels lnd ClosedChannels.defReq
    liftIO $
      res
        `shouldSatisfy` ( \case
                            Left {} -> False
                            Right xs -> any ((cp ==) . CloseChannel.chPoint) xs
                        )
  where
    --
    -- TODO : fix this, it's not really working for some reason
    -- LND says "payment isn't initiated" error in logs
    --
    --it "trackPaymentV2" $ withEnv $ do
    --  setupOneChannel
    --  --
    --  -- prepare invoice and subscription
    --  --
    --  bob <- getLndEnv Bob
    --  inv <- liftLndResult =<< addInvoice bob addInvoiceRequest
    --  let rh = AddInvoice.rHash inv
    --  let pr = AddInvoice.paymentRequest inv
    --  let spr = SendPaymentRequest pr $ MSat 1000
    --  --
    --  -- spawn payment watcher and settle invoice
    --  --
    --  w <- Watcher.spawnLink bob trackPaymentV2Chan $ \_ _ _ -> pure ()
    --  chan <- Watcher.dupLndTChan w
    --  Watcher.watch w $ TrackPaymentRequest rh False
    --  alice <- getLndEnv Alice
    --  void $ liftLndResult =<< sendPayment alice spr
    --  res <- readTChanTimeout (MicroSecondsDelay 500000) chan
    --  Watcher.delete w
    --  liftIO $ res `shouldSatisfy` isJust
    subscribeInvoicesRequest =
      SubscribeInvoicesRequest (Just $ AddIndex 1) Nothing
    addInvoiceRequest =
      AddInvoiceRequest
        { memo = Just "HELLO",
          valueMsat = MSat 1000000,
          expiry = Just $ Seconds 1000
        }