packages feed

morley-client-0.3.1: src/Morley/Client/Full.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

-- | Implementation of full-featured Morley client.

module Morley.Client.Full
  ( MorleyClientEnv(..)
  , MorleyClientConfig (..)
  , MorleyClientM
  , runMorleyClientM
  , mkMorleyClientEnv
  , mkLogAction
  -- * Lens
  , mceTezosClientL
  , mceLogActionL
  , mceSecretKeyL
  , mceClientEnvL
  , mccEndpointUrlL
  , mccTezosClientPathL
  , mccMbTezosClientDataDirL
  , mccVerbosityL
  , mccSecretKeyL
  ) where

import Colog (HasLog(..), Message)
import Network.HTTP.Types (Status(..))
import Servant.Client (ClientEnv)
import Servant.Client.Core (Request, Response, RunClient(..))
import System.Environment (lookupEnv)
import UnliftIO (MonadUnliftIO)

import Morley.Client.App
import Morley.Client.Init
import Morley.Client.Logging (ClientLogAction)
import Morley.Client.RPC.Class
import Morley.Client.RPC.HttpClient
import Morley.Client.TezosClient.Class
import Morley.Client.TezosClient.Impl (getTezosClientConfig)
import Morley.Client.TezosClient.Impl qualified as TezosClient
import Morley.Client.TezosClient.Types
import Morley.Client.Types
import Morley.Tezos.Crypto (Signature(..))
import Morley.Tezos.Crypto.Ed25519 qualified as Ed25519
import Morley.Util.Lens (makeLensesWith, postfixLFields)


-- | Runtime environment for morley client.
data MorleyClientEnv = MorleyClientEnv
  { mceTezosClient :: TezosClientEnv
  -- ^ Environment for @octez-client@.
  , mceLogAction :: ClientLogAction MorleyClientM
  -- ^ Action used to log messages.
  , mceSecretKey :: Maybe Ed25519.SecretKey
  -- ^ Pass if you want to sign operations manually or leave it
  -- to @octez-client@.
  , mceClientEnv :: ClientEnv
  -- ^ Environment necessary to make HTTP calls.
  }

newtype MorleyClientM a = MorleyClientM
  { unMorleyClientM :: ReaderT MorleyClientEnv IO a }
  deriving newtype
    ( Functor, Applicative, Monad, MonadReader MorleyClientEnv
    , MonadIO, MonadThrow, MonadCatch, MonadMask, MonadUnliftIO
    )

-- | Run 'MorleyClientM' action within given t'MorleyClientEnv'. Retry action
-- in case of invalid counter error.
runMorleyClientM :: MorleyClientEnv -> MorleyClientM a -> IO a
runMorleyClientM env client = runReaderT (unMorleyClientM client) env

makeLensesWith postfixLFields ''MorleyClientEnv

instance HasTezosClientEnv MorleyClientEnv where
  tezosClientEnvL = mceTezosClientL

instance HasLog MorleyClientEnv Message MorleyClientM where
  getLogAction = mceLogAction
  setLogAction action mce = mce { mceLogAction = action }

instance HasTezosClient MorleyClientM where
  signBytes (AddressWithAlias _ senderAlias) mbPassword opHash = retryOnceOnTimeout $ do
    env <- ask
    case mceSecretKey env of
      Just sk -> pure . SignatureEd25519 $ Ed25519.sign sk opHash
      Nothing -> TezosClient.signBytes senderAlias mbPassword opHash
  rememberContract = failOnTimeout ... TezosClient.rememberContract
  getAliasesAndAddresses = retryOnceOnTimeout ... TezosClient.getAliasesAndAddresses
  genKey alias = flip AddressWithAlias alias <$> TezosClient.genKey alias
  genFreshKey alias = flip AddressWithAlias alias <$> TezosClient.genFreshKey alias
  getKeyPassword (AddressWithAlias _ alias) = retryOnceOnTimeout $ TezosClient.getKeyPassword alias
  getPublicKey (AddressWithAlias _ alias) = TezosClient.getPublicKey alias

instance RunClient MorleyClientM where
  runRequestAcceptStatus :: Maybe [Status] -> Request -> MorleyClientM Response
  runRequestAcceptStatus statuses req = do
    env <- mceClientEnv <$> ask
    runRequestAcceptStatusImpl env statuses req
  throwClientError = throwClientErrorImpl

instance HasTezosRpc MorleyClientM where
  getBlockHash = getBlockHashImpl
  getCounterAtBlock = getCounterImpl
  getBlockHeader = getBlockHeaderImpl
  getBlockConstants = getBlockConstantsImpl
  getBlockOperations = getBlockOperationsImpl
  getScriptSizeAtBlock = getScriptSizeAtBlockImpl
  getBlockOperationHashes = getBlockOperationHashesImpl
  getProtocolParametersAtBlock = getProtocolParametersImpl
  runOperationAtBlock = runOperationImpl
  preApplyOperationsAtBlock = preApplyOperationsImpl
  forgeOperationAtBlock = forgeOperationImpl
  injectOperation = injectOperationImpl
  getContractScriptAtBlock = getContractScriptImpl
  getContractStorageAtBlock = getContractStorageAtBlockImpl
  getContractBigMapAtBlock = getContractBigMapImpl
  getBigMapValueAtBlock = getBigMapValueAtBlockImpl
  getBigMapValuesAtBlock = getBigMapValuesAtBlockImpl
  getBalanceAtBlock = getBalanceImpl
  getDelegateAtBlock = getDelegateImpl
  runCodeAtBlock = runCodeImpl
  getChainId = getChainIdImpl
  getManagerKeyAtBlock = getManagerKeyImpl
  waitForOperation = (asks mceClientEnv >>=) . waitForOperationImpl
  getTicketBalanceAtBlock = getTicketBalanceAtBlockImpl
  getAllTicketBalancesAtBlock = getAllTicketBalancesAtBlockImpl


-- | Construct 'MorleyClientEnv'.
--
-- * @octez-client@ path is taken from 'MorleyClientConfig', but can be
-- overridden using @MORLEY_TEZOS_CLIENT@ environment variable.
-- * Node data is taken from @octez-client@ config and can be overridden
-- by 'MorleyClientConfig'.
-- * The rest is taken from 'MorleyClientConfig' as is.
mkMorleyClientEnv :: MorleyClientConfig -> IO MorleyClientEnv
mkMorleyClientEnv MorleyClientConfig{..} = do
  envTezosClientPath <- lookupEnv "MORLEY_TEZOS_CLIENT"
  let tezosClientPath = fromMaybe mccTezosClientPath envTezosClientPath
  TezosClientConfig {..} <- getTezosClientConfig tezosClientPath mccMbTezosClientDataDir
  let
    endpointUrl = fromMaybe tcEndpointUrl mccEndpointUrl
    tezosClientEnv = TezosClientEnv
      { tceEndpointUrl = endpointUrl
      , tceTezosClientPath = tezosClientPath
      , tceMbTezosClientDataDir = mccMbTezosClientDataDir
      }

  clientEnv <- newClientEnv endpointUrl
  pure MorleyClientEnv
    { mceTezosClient = tezosClientEnv
    , mceLogAction = mkLogAction mccVerbosity
    , mceSecretKey = mccSecretKey
    , mceClientEnv = clientEnv
    }