Skip to content

Commit

Permalink
Wallet: tidying up for consistency
Browse files Browse the repository at this point in the history
- add comments
- remove redundant code
- remove unused logging data types
- introduce newtypes to avoid confusion
  • Loading branch information
gilligan committed Feb 23, 2021
1 parent c2313f0 commit b7f4676
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 241 deletions.
8 changes: 4 additions & 4 deletions plutus-pab/app/Cli.hs
Expand Up @@ -63,6 +63,7 @@ import qualified Cardano.Metadata.Server as Metadata
import qualified Cardano.Node.Server as NodeServer
import qualified Cardano.SigningProcess.Server as SigningProcess
import qualified Cardano.Wallet.Server as WalletServer
import Cardano.Wallet.Types
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (Async, async, waitAny)
import Control.Concurrent.Availability (Availability, starting)
Expand Down Expand Up @@ -90,8 +91,7 @@ import qualified Plutus.PAB.Core.ContractInstance as Instance
import Plutus.PAB.Events.Contract (ContractInstanceId (..))
import Plutus.PAB.PABLogMsg (AppMsg (..), ChainIndexServerMsg,
ContractExeLogMsg (..), MetadataLogMessage,
MockServerLogMsg, PABLogMsg (..), SigningProcessMsg,
WalletMsg)
MockServerLogMsg, PABLogMsg (..), SigningProcessMsg)
import Plutus.PAB.Types (Config (Config), ContractExe (..), PABError,
RequestProcessingConfig (..), chainIndexConfig,
metadataServerConfig, nodeServerConfig,
Expand All @@ -118,8 +118,8 @@ runCliCommand trace _ Config {..} serviceAvailability MockWallet =
liftIO $ WalletServer.main
(toWalletLog trace)
walletServerConfig
nodeUrl
chainIndexUrl
(NodeUrl nodeUrl)
(ChainIndexUrl chainIndexUrl)
serviceAvailability
where
nodeUrl = mscBaseUrl nodeServerConfig
Expand Down
165 changes: 75 additions & 90 deletions plutus-pab/src/Cardano/Wallet/Mock.hs
@@ -1,106 +1,91 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-deprecations #-}

module Cardano.Wallet.Mock where
module Cardano.Wallet.Mock
( processWalletEffects
) where

import Cardano.Wallet.Types (WalletId)
import Control.Lens (view)
import qualified Control.Monad.Except as MonadError
import Control.Monad.Freer
import Control.Monad.Freer.Error (Error, runError, throwError)
import Control.Monad.Freer.Extras.Log (LogMsg, logInfo)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Bifunctor (Bifunctor (..))
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Char8 as BSL8
import qualified Data.Map as Map
import Data.Text.Encoding (encodeUtf8)
import Data.Text.Prettyprint.Doc (Pretty (..), (<+>))
import Language.Plutus.Contract.Trace (allWallets)
import Ledger (Address, PubKey, TxOut (..), TxOutRef, TxOutTx (..), Value)
import Ledger.AddressMap (UtxoMap)
import qualified Ledger.AddressMap as AddressMap
import Plutus.PAB.Arbitrary ()
import Servant (ServerError, err401, err404, err500, errBody)
import Test.QuickCheck (arbitrary, generate)
import Wallet.API (WalletAPIError (InsufficientFunds, OtherError, PrivateKeyNotFound))
import Wallet.Effects (ChainIndexEffect)
import qualified Wallet.Effects as W
import Wallet.Emulator.Wallet (Wallet (Wallet))
import qualified Wallet.Emulator.Wallet as EM
import Control.Monad.Freer.Error
import Control.Monad.Freer.State (runState)
import Control.Monad.IO.Class (MonadIO, liftIO)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Char8 as BSL8
import qualified Data.ByteString.Lazy.Char8 as Char8
import Data.Function ((&))
import Data.Text.Encoding (encodeUtf8)
import Servant (ServerError (..), err400, err401, err404)
import Servant.Client (ClientEnv)

data MockWalletMsg =
CallWallets
| CallValueAt
| ValueAtResponse Address Value
| CallSelectCoin WalletId Value
| SelectCoinResult (Either WalletAPIError ([(TxOutRef, Value)], Value))
| CallAllocateAddress
import Cardano.BM.Data.Trace (Trace)
import qualified Cardano.ChainIndex.Client as ChainIndexClient
import qualified Cardano.Node.Client as NodeClient
import Cardano.Wallet.Types (WalletEffects, WalletMsg (..))
import Control.Concurrent (MVar)
import Control.Concurrent.MVar (putMVar, takeMVar)
import Control.Monad.Error (MonadError)
import Plutus.PAB.Arbitrary ()
import Plutus.PAB.Monitoring (convertLog, handleLogMsgTrace)
import Servant.Server (err500)
import Wallet.API (WalletAPIError (InsufficientFunds, OtherError, PrivateKeyNotFound))
import Wallet.Emulator.Wallet (WalletState)
import qualified Wallet.Emulator.Wallet as Wallet

instance Pretty MockWalletMsg where
pretty = \case
CallWallets -> "wallets"
CallValueAt -> "valueAt"
ValueAtResponse addr vl -> "valueAt" <+> pretty addr <> ":" <+> pretty vl
CallSelectCoin walletID target -> "selectCoin" <+> pretty walletID <+> pretty target
SelectCoinResult result -> "selectCoin result:" <+> pretty result
CallAllocateAddress -> "allocateAddress"

wallets :: (Member (LogMsg MockWalletMsg) effs) => Eff effs [Wallet]
wallets = do
logInfo CallWallets
pure allWallets
-- | Process wallet effects. Retain state and yield HTTP400 on error
-- or set new state on success.
processWalletEffects ::
(MonadIO m, MonadError ServerError m)
=> Trace IO WalletMsg -- ^ trace for logging
-> ClientEnv -- ^ node client
-> ClientEnv -- ^ chain index client
-> MVar WalletState -- ^ wallet state
-> Eff (WalletEffects IO) a -- ^ wallet effect
-> m a
processWalletEffects trace nodeClientEnv chainIndexEnv mVarState action = do
oldState <- liftIO $ takeMVar mVarState
result <- liftIO $ runWalletEffects trace nodeClientEnv chainIndexEnv oldState action
case result of
Left e -> do
liftIO $ putMVar mVarState oldState
MonadError.throwError $ err400 { errBody = Char8.pack (show e) }
Right (result_, newState) -> do
liftIO $ putMVar mVarState newState
pure result_

-- | Interpret wallet effects
runWalletEffects ::
MonadIO m
=> Trace m WalletMsg -- ^ trace for logging
-> ClientEnv -- ^ node client
-> ClientEnv -- ^ chain index client
-> WalletState -- ^ current state
-> Eff (WalletEffects m) a -- ^ wallet effect
-> m (Either ServerError (a, WalletState))
runWalletEffects trace nodeClientEnv chainIndexEnv walletState action =
Wallet.handleWallet action
& interpret (NodeClient.handleNodeClientClient nodeClientEnv)
& interpret (ChainIndexClient.handleChainIndexClient chainIndexEnv)
& runState walletState
& handleLogMsgTrace (toWalletMsg trace)
& handleWalletApiErrors
& handleClientErrors
& runError
& runM
where
handleWalletApiErrors = flip handleError (throwError . fromWalletAPIError)
handleClientErrors = flip handleError (\e -> throwError $ err500 { errBody = Char8.pack (show e) })
toWalletMsg = convertLog ChainClientMsg

-- | Convert Wallet errors to Servant error responses
fromWalletAPIError :: WalletAPIError -> ServerError
fromWalletAPIError (InsufficientFunds text) =
err401 {errBody = BSL.fromStrict $ encodeUtf8 text}
fromWalletAPIError err@(PrivateKeyNotFound _) =
err404 {errBody = BSL8.pack $ show err}
fromWalletAPIError e@(PrivateKeyNotFound _) =
err404 {errBody = BSL8.pack $ show e}
fromWalletAPIError (OtherError text) =
err500 {errBody = BSL.fromStrict $ encodeUtf8 text}

valueAt ::
( Member (LogMsg MockWalletMsg) effs
, Member ChainIndexEffect effs
)
=> Address
-> Eff effs Value
valueAt address = do
logInfo CallValueAt
value <- foldMap (txOutValue . txOutTxOut) . view (AddressMap.fundsAt address) <$> W.watchedAddresses
logInfo $ ValueAtResponse address value
pure value

selectCoin ::
( Member (LogMsg MockWalletMsg) effs
, Member ChainIndexEffect effs
, Member (Error ServerError) effs
)
=> WalletId
-> Value
-> Eff effs ([(TxOutRef, Value)], Value)
selectCoin walletId target = do
logInfo $ CallSelectCoin walletId target
let address = EM.walletAddress (Wallet walletId)
utxos :: UtxoMap <- view (AddressMap.fundsAt address) <$> W.watchedAddresses
let funds :: [(TxOutRef, Value)]
funds = fmap (second (txOutValue . txOutTxOut)) . Map.toList $ utxos
result <- runM $ runError $ EM.selectCoin funds target
logInfo $ SelectCoinResult result
case result of
Right value -> pure value
Left err -> throwError $ fromWalletAPIError err

allocateAddress ::
( LastMember m effs
, Member (LogMsg MockWalletMsg) effs
, MonadIO m
)
=> WalletId
-> Eff effs PubKey
allocateAddress _ = do
logInfo CallAllocateAddress
sendM $ liftIO $ generate arbitrary
118 changes: 29 additions & 89 deletions plutus-pab/src/Cardano/Wallet/Server.hs
Expand Up @@ -3,121 +3,61 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}

module Cardano.Wallet.Server
( main
, Config(..)
) where

import Cardano.BM.Data.Trace (Trace)
import qualified Cardano.ChainIndex.Client as ChainIndexClient
import qualified Cardano.Node.Client as NodeClient
import Cardano.Wallet.API (API)
import Cardano.Wallet.Mock
import Cardano.Wallet.Types (ChainIndexUrl, Config (..), NodeUrl, WalletMsg (..))
import Control.Concurrent.Availability (Availability, available)
import Control.Concurrent.MVar (MVar, newMVar, putMVar, takeMVar)
import Control.Monad ((>=>))
import qualified Control.Monad.Except as MonadError
import Control.Monad.Freer (Eff, interpret, reinterpret, runM)
import Control.Monad.Freer.Error (Error, handleError, runError, throwError)
import Control.Monad.Freer.Extras.Log (LogMsg, logInfo)
import Control.Monad.Freer.State (State, runState)
import Control.Monad.IO.Class (MonadIO, liftIO)
import qualified Data.ByteString.Lazy.Char8 as Char8
import Control.Monad.Freer (reinterpret, runM)
import Control.Monad.Freer.Error (handleError)
import Control.Monad.Freer.Extras.Log (logInfo)
import Control.Monad.IO.Class (liftIO)
import Data.Function ((&))
import Data.Proxy (Proxy (Proxy))
import Data.Text (Text)
import Network.HTTP.Client (defaultManagerSettings, newManager)
import qualified Network.Wai.Handler.Warp as Warp
import Plutus.PAB.Arbitrary ()
import Servant (Application, Handler (Handler), NoContent (..), ServerError (..),
err400, err500, hoistServer, serve, (:<|>) ((:<|>)))
import Servant (Application, NoContent (..), hoistServer, serve, (:<|>) ((:<|>)))
import Servant.Client (BaseUrl (baseUrlPort), ClientEnv, ClientError, mkClientEnv)

import Plutus.PAB.Monitoring (convertLog, handleLogMsgTrace, runLogEffects)
import Wallet.Effects (ChainIndexEffect, NodeClientEffect, WalletEffect, ownOutputs,
ownPubKey, startWatching, submitTxn, updatePaymentWithChange,
walletSlot)
import Wallet.Emulator.Error (WalletAPIError)

import Cardano.BM.Data.Trace (Trace)
import qualified Cardano.ChainIndex.Client as ChainIndexClient
import Cardano.Wallet.API (API)
import Cardano.Wallet.Mock
import Cardano.Wallet.Types (ChainIndexUrl (..), NodeUrl (..), Port (..), WalletConfig (..),
WalletMsg (..))
import Control.Concurrent.Availability (Availability, available)
import Control.Concurrent.MVar (MVar, newMVar)
import Plutus.PAB.Arbitrary ()
import Plutus.PAB.Monitoring (runLogEffects)
import Wallet.Effects (ownOutputs, ownPubKey, startWatching, submitTxn,
updatePaymentWithChange, walletSlot)
import Wallet.Emulator.Wallet (WalletState, emptyWalletState)
import qualified Wallet.Emulator.Wallet as Wallet

type AppEffects m = '[WalletEffect
, NodeClientEffect
, ChainIndexEffect
, State WalletState
, LogMsg Text
, Error WalletAPIError
, Error ClientError
, Error ServerError
, m]
runAppEffects ::
MonadIO m
=> Trace m WalletMsg
-> ClientEnv
-> ClientEnv
-> WalletState
-> Eff (AppEffects m) a
-> m (Either ServerError (a, WalletState))
runAppEffects trace nodeClientEnv chainIndexEnv walletState action =
Wallet.handleWallet action
& interpret (NodeClient.handleNodeClientClient nodeClientEnv)
& interpret (ChainIndexClient.handleChainIndexClient chainIndexEnv)
& runState walletState
& handleLogMsgTrace (toWalletMsg trace)
& handleWalletApiErrors
& handleClientErrors
& runError
& runM
where
handleWalletApiErrors = flip handleError (throwError . fromWalletAPIError)
handleClientErrors = flip handleError (\e -> throwError $ err500 { errBody = Char8.pack (show e) })
toWalletMsg = convertLog ChainClientMsg


------------------------------------------------------------
-- | Run all handlers, affecting a single, global 'MVar WalletState'.
--
-- Note this code is pretty simplistic, as it makes every handler
-- block on access to a single, global 'MVar'. We could do something
-- smarter, but I don't think it matters as this is only a mock.
asHandler ::
Trace IO WalletMsg
-> ClientEnv
-> ClientEnv
-> MVar WalletState
-> Eff (AppEffects IO) a
-> Handler a
asHandler trace nodeClientEnv chainIndexEnv mVarState action =
Handler $ do
oldState <- liftIO $ takeMVar mVarState
result <- liftIO $ runAppEffects trace nodeClientEnv chainIndexEnv oldState action
case result of
Left err -> do
liftIO $ putMVar mVarState oldState
MonadError.throwError $ err400 { errBody = Char8.pack (show err) }
Right (result_, newState) -> do
liftIO $ putMVar mVarState newState
pure result_

app :: Trace IO WalletMsg -> ClientEnv -> ClientEnv -> MVar WalletState -> Application
app trace nodeClientEnv chainIndexEnv mVarState =
serve (Proxy @API) $
hoistServer (Proxy @API) (asHandler trace nodeClientEnv chainIndexEnv mVarState) $
(submitTxn >=> const (pure NoContent)) :<|> ownPubKey :<|> uncurry updatePaymentWithChange :<|>
walletSlot :<|> ownOutputs
hoistServer
(Proxy @API)
(processWalletEffects trace nodeClientEnv chainIndexEnv mVarState)
((submitTxn >=> const (pure NoContent)) :<|> ownPubKey :<|> uncurry updatePaymentWithChange :<|>
walletSlot :<|> ownOutputs)

main :: Trace IO WalletMsg -> Config -> NodeUrl -> ChainIndexUrl -> Availability -> IO ()
main trace Config {..} nodeBaseUrl chainIndexBaseUrl availability = runLogEffects trace $ do
nodeClientEnv <- buildEnv nodeBaseUrl defaultManagerSettings
chainIndexEnv <- buildEnv chainIndexBaseUrl defaultManagerSettings
main :: Trace IO WalletMsg -> WalletConfig -> NodeUrl -> ChainIndexUrl -> Availability -> IO ()
main trace WalletConfig { baseUrl, wallet } (NodeUrl nodeUrl) (ChainIndexUrl chainUrl) availability = runLogEffects trace $ do
nodeClientEnv <- buildEnv nodeUrl defaultManagerSettings
chainIndexEnv <- buildEnv chainUrl defaultManagerSettings
mVarState <- liftIO $ newMVar state
runClient chainIndexEnv
logInfo $ StartingWallet servicePort
logInfo $ StartingWallet (Port servicePort)
liftIO $ Warp.runSettings warpSettings $ app trace nodeClientEnv chainIndexEnv mVarState
where
servicePort = baseUrlPort baseUrl
Expand Down

0 comments on commit b7f4676

Please sign in to comment.