packages feed

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

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

module LndClient.RPCSpec
  ( spec,
  )
where

import qualified Control.Concurrent.Async as Async
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 qualified LndClient.Data.PayReq as PayReq
import LndClient.Data.Payment as Payment (Payment (..), PaymentStatus (..))
import LndClient.Data.PendingChannels (PendingChannelsResponse (..))
import LndClient.Data.SendPayment (SendPaymentRequest (..))
import LndClient.Data.SignMessage
import LndClient.Data.SubscribeInvoices
  ( SubscribeInvoicesRequest (..),
  )
import qualified LndClient.Data.TrackPayment as TrackPayment
import LndClient.Data.VerifyMessage
import qualified LndClient.Data.WalletBalance as Wallet
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
      ct <- liftIO getCurrentTime
      GetInfoResponse pub _ _ <- liftLndResult =<< getInfo lnd
      x1 <-
        liftLndResult
          =<< decodePayReq lnd (AddInvoice.paymentRequest x0)
      let dt =
            picosecondsToDiffTime
              . (* 1000000000000)
              . (`div` 1000000000000)
              . diffTimeToPicoseconds
              $ utctDayTime ct
      let expSec = Seconds 1000
      let utc = ct {utctDayTime = dt}
      liftIO $
        x1
          `shouldBe` PayReq.PayReq
            { PayReq.destination = pub,
              PayReq.paymentHash = AddInvoice.rHash x0,
              PayReq.numMsat = AddInvoice.valueMsat addInvoiceRequest,
              PayReq.expiry = Seconds 1000,
              PayReq.timestamp = ct {utctDayTime = dt},
              PayReq.expiresAt = PayReq.addSeconds expSec utc
            }
      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
          lnd
          (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 bob rh Invoice.OPEN chan
      alice <- getLndEnv Alice
      let pr = AddInvoice.paymentRequest inv
      let spr = SendPaymentRequest pr (MSat 1000000) Nothing
      void $ liftLndResult =<< sendPayment alice spr
      res <- receiveInvoice bob 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
      Watcher.withWatcher bob subscribeInvoicesChan ignore3 $ \w -> do
        chan <- Watcher.dupLndTChan w
        Watcher.watch w subscribeInvoicesRequest
        inv <- liftLndResult =<< addInvoice bob addInvoiceRequest
        res <-
          receiveInvoice
            bob
            (AddInvoice.rHash inv)
            Invoice.OPEN
            chan
        liftIO $ res `shouldSatisfy` isRight
  it "watchUnit" $
    withEnv $ do
      bob <- getLndEnv Bob
      alice <- getLndEnv Alice
      Watcher.withWatcherUnit bob subscribeChannelEventsChan ignore2 $ \w -> do
        chan <- Watcher.dupLndTChan w
        Watcher.watchUnit w
        GetInfoResponse bobPubKey _ _ <- liftLndResult =<< getInfo bob
        let openChannelRequest =
              OpenChannelRequest
                { nodePubkey = bobPubKey,
                  localFundingAmount = MSat 20000000,
                  pushMSat = Just $ MSat 1000000,
                  targetConf = Nothing,
                  mSatPerByte = Nothing,
                  private = Nothing,
                  minHtlcMsat = Nothing,
                  remoteCsvDelay = Nothing,
                  minConfs = Nothing,
                  spendUnconfirmed = Nothing,
                  closeAddress = Nothing,
                  fundingShim = Nothing
                }
        cp <- liftLndResult =<< openChannelSync alice openChannelRequest
        res <- receiveActiveChannel proxyOwner cp chan
        liftIO $ res `shouldSatisfy` isRight
  it "unWatch" $
    withEnv $ do
      lnd <- getLndEnv Bob
      Watcher.withWatcher
        lnd
        subscribeInvoicesChan
        -- can unWatch from the callback
        (\w k _ -> Watcher.unWatch w k)
        $ \w -> do
          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
          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 bob rh Invoice.OPEN q
      let spr = SendPaymentRequest pr (MSat 1000000) Nothing
      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 bob rh Invoice.ACCEPTED qq
            res <- cancelInvoice bob rh
            liftLndResult
              =<< receiveInvoice bob 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 bob rh Invoice.OPEN q
      let spr = SendPaymentRequest pr (MSat 1000000) Nothing
      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 bob rh Invoice.ACCEPTED qq
            res <- settleInvoice bob r
            liftLndResult
              =<< receiveInvoice bob 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 = True,
                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 <- liftLndResult =<< closedChannels lnd ClosedChannels.defReq
      liftIO $ res `shouldSatisfy` any ((cp ==) . CloseChannel.chPoint)
  it "subscriptionCanBeCancelled" $
    withEnv $ do
      bob <- getLndEnv Bob
      alice <- getLndEnv Alice
      GetInfoResponse bobPubKey _ _ <- liftLndResult =<< getInfo bob
      let openChannelRequest =
            OpenChannelRequest
              { nodePubkey = bobPubKey,
                localFundingAmount = MSat 20000000,
                pushMSat = Just $ MSat 1000000,
                targetConf = Nothing,
                mSatPerByte = Nothing,
                private = Nothing,
                minHtlcMsat = Nothing,
                remoteCsvDelay = Nothing,
                minConfs = Nothing,
                spendUnconfirmed = Nothing,
                closeAddress = Nothing,
                fundingShim = Nothing
              }
      a <-
        spawnLink $
          liftLndResult
            =<< openChannel (const $ return ()) alice openChannelRequest
      liftIO $ do
        res <- Async.race (sleep $ MicroSecondsDelay 100000) $ Async.cancel a
        res `shouldSatisfy` isRight
  it "trackPaymentV2" $
    withEnv $
      do
        void $ setupOneChannel Alice Bob
        --
        -- prepare invoice and subscription
        --
        alice <- getLndEnv Alice
        bob <- getLndEnv Bob
        inv <- liftLndResult =<< addInvoice bob addInvoiceRequest
        let req =
              SendPaymentRequest
                (AddInvoice.paymentRequest inv)
                (AddInvoice.valueMsat addInvoiceRequest)
                Nothing
        --
        -- spawn payment watcher and settle invoice
        --
        Watcher.withWatcher alice trackPaymentV2Chan (\_ _ _ -> pure ()) $ \w -> do
          void $ liftLndResult =<< sendPayment alice req
          chan <- Watcher.dupLndTChan w
          Watcher.watch w $
            TrackPayment.TrackPaymentRequest (AddInvoice.rHash inv) False
          res <- readTChanTimeout (MicroSecondsDelay 2000000) chan
          liftIO $ res `shouldSatisfy` isJust
  it "signVerify" $
    withEnv $ do
      alice <- getLndEnv Alice
      bob <- getLndEnv Bob
      --
      -- TODO : verifyMessage will work only in case
      -- where both (double-hash and compact) flags
      -- are set to False. Investigate why.
      --
      SignMessageResponse sig <-
        liftLndResult
          =<< signMessage alice (SignMessageRequest "test" (KeyLocator 6 0) False False)
      GetInfoResponse pubKey _ _ <-
        liftLndResult
          =<< getInfo alice
      VerifyMessageResponse res <-
        liftLndResult
          =<< verifyMessage bob (VerifyMessageRequest "test" sig (coerce pubKey))
      liftIO $
        res `shouldBe` True
  it "waitForGrpc" $
    withEnv $ do
      res <- waitForGrpc =<< getLndEnv Alice
      liftIO $ res `shouldSatisfy` isRight
  it "setupChannelAndClose" $
    withEnv $ do
      lndAlice <- getLndEnv Alice
      lndBob <- getLndEnv Bob
      GetInfoResponse merchantPubKey _ _ <-
        liftLndResult =<< getInfo lndBob
      let openChannelRequest =
            OpenChannelRequest
              { nodePubkey = merchantPubKey,
                localFundingAmount = MSat 200000000,
                pushMSat = Just $ MSat 10000000,
                targetConf = Nothing,
                mSatPerByte = Nothing,
                private = Nothing,
                minHtlcMsat = Nothing,
                remoteCsvDelay = Nothing,
                minConfs = Nothing,
                spendUnconfirmed = Nothing,
                closeAddress = Nothing,
                fundingShim = Nothing
              }
      _ <-
        liftLndResult
          =<< openChannelSync lndAlice openChannelRequest
      sleep $ MicroSecondsDelay 100000
      res <- pendingChannels =<< getLndEnv Alice
      let pc = case res of
            Left {} -> fail "Pending channels fail"
            Right (PendingChannelsResponse _ x _ _ _) -> x
      liftIO $ pc `shouldNotSatisfy` null
  it "trackPaymentV2Sync SUCCEEDED" $
    withEnv $
      do
        void $ setupOneChannel Alice Bob
        alice <- getLndEnv Alice
        bob <- getLndEnv Bob
        inv <- liftLndResult =<< addInvoice bob addInvoiceRequest
        let req =
              SendPaymentRequest
                (AddInvoice.paymentRequest inv)
                (AddInvoice.valueMsat addInvoiceRequest)
                Nothing
        let tpreq = TrackPayment.TrackPaymentRequest (AddInvoice.rHash inv) False
        _ <- sendPayment alice req
        tp <- liftLndResult =<< trackPaymentSync alice tpreq
        liftIO $ Payment.state tp `shouldBe` Payment.SUCCEEDED
  it "trackPaymentV2Sync FAILED" $
    withEnv $
      do
        void $ setupZeroChannels proxyOwner
        alice <- getLndEnv Alice
        bob <- getLndEnv Bob
        inv <- liftLndResult =<< addInvoice bob addInvoiceRequest
        let req =
              SendPaymentRequest
                (AddInvoice.paymentRequest inv)
                (AddInvoice.valueMsat addInvoiceRequest)
                Nothing
        let tpreq = TrackPayment.TrackPaymentRequest (AddInvoice.rHash inv) False
        _ <- sendPayment alice req
        tp <- liftLndResult =<< trackPaymentSync alice tpreq
        liftIO $ Payment.state tp `shouldBe` Payment.FAILED
  it "walletBalance" $
    withEnv $ do
      void $ setupOneChannel Alice Bob
      lnd <- getLndEnv Alice
      res <- liftLndResult =<< walletBalance lnd
      print res
      liftIO $ do
        Wallet.totalBalance res `shouldSatisfy` (> 0)
        Wallet.confirmedBalance res `shouldSatisfy` (> 0)
        Wallet.unconfirmedBalance res `shouldBe` 0
  where
    subscribeInvoicesRequest =
      SubscribeInvoicesRequest (Just $ AddIndex 1) Nothing
    addInvoiceRequest =
      AddInvoiceRequest
        { memo = Just "HELLO",
          valueMsat = MSat 1000000,
          expiry = Just $ Seconds 1000
        }