packages feed

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

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-}

module LndClient.PsbtSpec
  ( spec,
  )
where

import qualified Data.Map as M
import qualified LndClient.Data.FinalizePsbt as FNP
import qualified LndClient.Data.FundPsbt as FP
import qualified LndClient.Data.FundingPsbtFinalize as FPF
import qualified LndClient.Data.FundingPsbtVerify as FSS
import qualified LndClient.Data.FundingStateStep as FSS
import LndClient.Data.GetInfo (GetInfoResponse (..))
import qualified LndClient.Data.ListUnspent as LU
import qualified LndClient.Data.NewAddress as NA
import LndClient.Data.OpenChannel
import qualified LndClient.Data.OutPoint as OP
import qualified LndClient.Data.PsbtShim as PS
import qualified LndClient.Data.PublishTransaction as PT
import qualified LndClient.Data.SendCoins as SC
import LndClient.Import
import LndClient.LndTest
import LndClient.RPC.Katip
import LndClient.TestApp
import Test.Hspec
import qualified UnliftIO.STM as T
import LndClient.Data.ChannelPoint

genAddr :: (KatipContext f, MonadUnliftIO f) => LndEnv -> f Text
genAddr lnd =
  fmap NA.address $
    liftLndResult =<< newAddress lnd (NA.NewAddressRequest NA.WITNESS_PUBKEY_HASH Nothing)

findUtxosByTxId :: (KatipContext m, MonadUnliftIO m) => LndEnv -> ByteString -> m [LU.Utxo]
findUtxosByTxId lnd txid' = do
  utxos <- LU.utxos <$> (liftLndResult =<< listUnspent lnd (LU.ListUnspentRequest 0 10 ""))
  pure $ filter (\u -> txid' == OP.txid (LU.outpoint u)) utxos

psbtVerifyReq :: PendingChannelId -> Psbt -> FSS.FundingStateStepRequest
psbtVerifyReq pcid fp =
  FSS.FundingStateStepPsbtVerifyRequest $
    FSS.FundingPsbtVerify
      { FSS.pendingChanId = pcid,
        FSS.fundedPsbt = fp,
        FSS.skipFinalize = False
      }

psbtFinalizeReq :: PendingChannelId -> Psbt -> FSS.FundingStateStepRequest
psbtFinalizeReq pcid sp =
  FSS.FundingStateStepPsbtFinalizeRequest $
    FPF.FundingPsbtFinalize
      { FPF.signedPsbt = sp,
        FPF.pendingChanId = pcid,
        FPF.finalRawTx = RawTx ""
      }

fundPsbtToAddr :: LndTest m Owner => Text -> MSat -> m FP.FundPsbtResponse
fundPsbtToAddr fAddr amt = do
  lndBob <- getLndEnv Bob
  lndAlice <- getLndEnv Alice
  addrAlice <- genAddr lndAlice
  mine 10 Bob
  let bankAmt = amt * 2
  sendTrx <- liftLndResult =<< sendCoins lndBob (SC.SendCoinsRequest addrAlice bankAmt False)
  mine 2 Bob
  txid <- liftLndResult $ txIdParser $ SC.txid sendTrx
  utxos <- findUtxosByTxId lndAlice txid
  --print $ "Found in Alice unspent list:" ++ show utxos
  let temp = FP.TxTemplate (LU.outpoint <$> utxos) (M.fromList [(fAddr, amt)])
  let fr = FP.FundPsbtRequest "" temp 2 False (FP.SatPerVbyte 2)
  liftLndResult =<< fundPsbt lndAlice fr

signPsbt :: LndTest m Owner => FP.FundPsbtResponse -> m FNP.FinalizePsbtResponse
signPsbt psbt' = do
  lndAlice <- getLndEnv Alice
  liftLndResult =<< finalizePsbt lndAlice (FNP.FinalizePsbtRequest (FP.fundedPsbt psbt') "")

openChannelPsbt :: (LndTest m Owner) => LndEnv -> NodePubKey -> MSat -> m (Either Text ChannelPoint)
openChannelPsbt lndEnv toPubKey locFundAmt = do
  chan <- T.newTChanIO
  pcid <- newPendingChanId
  let psbtShim =
        PS.PsbtShim
          { PS.pendingChanId = pcid,
            PS.basePsbt = Nothing,
            PS.noPublish = False
          }
  let openChannelRequest =
        OpenChannelRequest
          { nodePubkey = toPubKey,
            localFundingAmount = locFundAmt,
            pushMSat = Nothing,
            targetConf = Nothing,
            mSatPerByte = Nothing,
            private = Nothing,
            minHtlcMsat = Nothing,
            remoteCsvDelay = Nothing,
            minConfs = Nothing,
            spendUnconfirmed = Nothing,
            closeAddress = Nothing,
            fundingShim = Just psbtShim
          }
  void $ spawnLink $ liftLndResult =<< openChannel (void . T.atomically . T.writeTChan chan) lndEnv openChannelRequest
  fundStep pcid chan
  where
    fundStep pcid chan = do
      upd <- T.atomically $ T.readTChan chan
      $(logTM) DebugS $ logStr $ "Got chan status update" <> inspect upd
      case upd of
        OpenStatusUpdate _ (Just (OpenStatusUpdatePsbtFund (ReadyForPsbtFunding faddr famt _))) -> do
          $(logTM) DebugS $ logStr $ "Chan ready for funding at addr:" <> inspect faddr <> " with amt:" <> inspect famt
          psbtResp <- fundPsbtToAddr faddr famt
          let psbt' = Psbt $ FP.fundedPsbt psbtResp
          void $ liftLndResult =<< fundingStateStep lndEnv (psbtVerifyReq pcid psbt')
          sPsbtResp <- signPsbt psbtResp
          $(logTM) DebugS $ logStr $ "Used psbt for funding:" <> inspect sPsbtResp
          void $ liftLndResult =<< fundingStateStep lndEnv (psbtFinalizeReq pcid (Psbt $ FNP.signedPsbt sPsbtResp))
          fundStep pcid chan
        OpenStatusUpdate _ (Just (OpenStatusUpdateChanPending p)) -> do
          $(logTM) DebugS $ logStr $ "Chan is pending... mining..." <> inspect p
          mine 3 Bob
          fundStep pcid chan
        OpenStatusUpdate _ (Just (OpenStatusUpdateChanOpen (ChannelOpenUpdate cp))) -> do
          $(logTM) DebugS $ logStr $ "Chan is open" <> inspect cp
          pure (Right cp)
        _ -> pure (Left "Unexpected update")

spec :: Spec
spec = do
  it "fundPsbt" $
    withEnv $ do
      lndBob <- getLndEnv Bob
      lndAlice <- getLndEnv Alice
      addrAlice <- genAddr lndAlice
      addrBob <- genAddr lndBob
      --print $ "Alice addr: " <> addrAlice
      --print $ "Bob addr: " <> addrBob
      mine 10 Bob
      let amt = MSat 200000000
      let psbtBackAmt = MSat 100000000
      sendTrx <- liftLndResult =<< sendCoins lndBob (SC.SendCoinsRequest addrAlice amt False)
      --print $ "Bob sends to Alice:" ++ show amt ++ " with txid: " ++ show (SC.txid sendTrx)
      mine 2 Bob
      txid <- liftLndResult $ txIdParser $ SC.txid sendTrx
      utxos <- findUtxosByTxId lndAlice txid
      --print $ "Found in Alice unspent list:" ++ show utxos
      let temp = FP.TxTemplate (LU.outpoint <$> utxos) (M.fromList [(addrBob, psbtBackAmt)])
      let fr = FP.FundPsbtRequest "" temp 2 False (FP.SatPerVbyte 2)
      psbt' <- liftLndResult =<< fundPsbt lndAlice fr
      fin <- liftLndResult =<< finalizePsbt lndAlice (FNP.FinalizePsbtRequest (FP.fundedPsbt psbt') "")
      --print $ "Final psbt from Alice:" ++ show finPsbt
      let txPsbt = FNP.rawFinalTx fin
      --print $ "Final transaction to publish" ++ show (txIdHex txPsbt)
      res <-
        liftLndResult
          =<< publishTransaction lndAlice (PT.PublishTransactionRequest txPsbt "hehe")
      mine 1 Bob
      liftIO $ shouldBe (PT.publishError res) ""
  it "openChannel with psbt" $
    withEnv $ do
      bob <- getLndEnv Bob
      alice <- getLndEnv Alice
      GetInfoResponse bobPubKey _ _ <- liftLndResult =<< getInfo bob
      cp <- openChannelPsbt alice bobPubKey (MSat 266600000)
      liftIO $ shouldSatisfy cp isRight