diff --git a/changelog.md b/changelog.md index 23bcb6fa..082d8ace 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Convex.MockChain`: Support for profiling plutus scripts. `evaluateTx` returns the script contexts for a transaction. These can be turned into a fully applied script with `fullyAppliedScript` * Add `querySlotNo` to `MonadBlockchain` typeclass and update both blockchain and mockchain implementations. * Add `utcTimeToPosixTime` in `Convex.Utils`. +* Considering explicit error type `MonadBlockchainError` for `MonadBlockchainCardanoNodeT` to enable proper error handling by caller. ## [0.0.1] - 2023-04-26 diff --git a/src/base/lib/Convex/Class.hs b/src/base/lib/Convex/Class.hs index 4573365e..e92ec4c2 100644 --- a/src/base/lib/Convex/Class.hs +++ b/src/base/lib/Convex/Class.hs @@ -8,6 +8,7 @@ module Convex.Class( MonadBlockchain(..), MonadMockchain(..), + MonadBlockchainError(..), getSlot, setSlot, setPOSIXTime, @@ -36,7 +37,10 @@ import Cardano.Ledger.Shelley.API (UTxO) import Cardano.Slotting.Time (SystemStart, SlotLength) import Control.Lens (_1, view) -import Control.Monad.Except (MonadError) +import Control.Monad.Except (MonadError, + catchError, + runExceptT, + throwError) import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Reader (MonadTrans, ReaderT (..), @@ -188,17 +192,29 @@ setTimeToValidRange = \case nextSlot :: MonadMockchain m => m () nextSlot = modifySlot (\s -> (succ s, ())) +data MonadBlockchainError e = + MonadBlockchainError e + | FailWith String + deriving (Eq) + +instance Show e => Show (MonadBlockchainError e) where + show (MonadBlockchainError e) = show e + show (FailWith str) = str + {-| 'MonadBlockchain' implementation that connects to a cardano node -} -newtype MonadBlockchainCardanoNodeT m a = MonadBlockchainCardanoNodeT { unMonadBlockchainCardanoNodeT :: ReaderT (LocalNodeConnectInfo CardanoMode) m a } - deriving newtype (Functor, Applicative, Monad, MonadTrans, MonadIO) +newtype MonadBlockchainCardanoNodeT e m a = MonadBlockchainCardanoNodeT { unMonadBlockchainCardanoNodeT :: ReaderT (LocalNodeConnectInfo CardanoMode) (ExceptT (MonadBlockchainError e) m) a } + deriving newtype (Functor, Applicative, Monad, MonadIO) -deriving newtype instance MonadError e m => MonadError e (MonadBlockchainCardanoNodeT m) +instance MonadError e m => MonadError e (MonadBlockchainCardanoNodeT e m) where + throwError = lift . throwError + catchError m _ = m -runMonadBlockchainCardanoNodeT :: LocalNodeConnectInfo CardanoMode -> MonadBlockchainCardanoNodeT m a -> m a -runMonadBlockchainCardanoNodeT info (MonadBlockchainCardanoNodeT action) = runReaderT action info -runQuery :: (MonadIO m, MonadLog m, MonadFail m) => C.QueryInMode CardanoMode a -> MonadBlockchainCardanoNodeT m a +runMonadBlockchainCardanoNodeT :: LocalNodeConnectInfo CardanoMode -> MonadBlockchainCardanoNodeT e m a -> m (Either (MonadBlockchainError e) a) +runMonadBlockchainCardanoNodeT info (MonadBlockchainCardanoNodeT action) = runExceptT (runReaderT action info) + +runQuery :: (MonadIO m, MonadLog m) => C.QueryInMode CardanoMode a -> MonadBlockchainCardanoNodeT e m a runQuery qry = MonadBlockchainCardanoNodeT $ do info <- ask result <- liftIO (C.queryNodeLocalState info Nothing qry) @@ -206,21 +222,21 @@ runQuery qry = MonadBlockchainCardanoNodeT $ do Left err -> do let msg = "runQuery: Query failed: " <> show err logWarnS msg - fail msg + throwError $ FailWith msg Right result' -> do pure result' -runQuery' :: (MonadIO m, MonadLog m, Show err, MonadFail m) => C.QueryInMode CardanoMode (Either err a) -> MonadBlockchainCardanoNodeT m a +runQuery' :: (MonadIO m, MonadLog m, Show e1) => C.QueryInMode CardanoMode (Either e1 a) -> MonadBlockchainCardanoNodeT e2 m a runQuery' qry = runQuery qry >>= \case Left err -> MonadBlockchainCardanoNodeT $ do let msg = "runQuery': Era mismatch: " <> show err logWarnS msg - fail msg + throwError $ FailWith msg Right result' -> MonadBlockchainCardanoNodeT $ do logInfoS "runQuery': Success" pure result' -instance (MonadFail m, MonadLog m, MonadIO m) => MonadBlockchain (MonadBlockchainCardanoNodeT m) where +instance (MonadLog m, MonadIO m) => MonadBlockchain (MonadBlockchainCardanoNodeT e m) where sendTx tx = MonadBlockchainCardanoNodeT $ do let txId = C.getTxId (C.getTxBody tx) info <- ask @@ -229,9 +245,11 @@ instance (MonadFail m, MonadLog m, MonadIO m) => MonadBlockchain (MonadBlockchai case result of SubmitSuccess -> do logInfoS ("sendTx: Submitted " <> show txId) + pure txId SubmitFail reason -> do - logWarnS $ "sendTx: Submission failed: " <> show reason - pure txId + let msg = "sendTx: Submission failed: " <> show reason + logWarnS msg + throwError $ FailWith msg utxoByTxIn txIns = runQuery' (C.QueryInEra C.BabbageEraInCardanoMode (C.QueryInShelleyBasedEra C.ShelleyBasedEraBabbage (C.QueryUTxO (C.QueryUTxOByTxIn txIns)))) @@ -248,25 +266,27 @@ instance (MonadFail m, MonadLog m, MonadIO m) => MonadBlockchain (MonadBlockchai queryEraHistory = runQuery (C.QueryEraHistory C.CardanoModeIsMultiEra) querySlotNo = do - let logErr err = do - let msg = "querySlotNo: Failed with " <> err - logWarnS msg - fail msg (eraHistory@(EraHistory _ interpreter), systemStart) <- (,) <$> queryEraHistory <*> querySystemStart slotNo <- runQuery (C.QueryChainPoint C.CardanoMode) >>= \case C.ChainPointAtGenesis -> pure $ fromIntegral (0 :: Integer) C.ChainPoint slot _hsh -> pure slot - utctime <- either logErr pure (slotToUtcTime eraHistory systemStart slotNo) - either (logErr . show) (\l -> pure (slotNo, l, utctime)) (interpretQuery interpreter $ slotToSlotLength slotNo) + MonadBlockchainCardanoNodeT $ do + let logErr err = do + let msg = "querySlotNo: Failed with " <> err + logWarnS msg + throwError $ FailWith msg + utctime <- either logErr pure (slotToUtcTime eraHistory systemStart slotNo) + either (logErr . show) (\l -> pure (slotNo, l, utctime)) (interpretQuery interpreter $ slotToSlotLength slotNo) networkId = MonadBlockchainCardanoNodeT (asks C.localNodeNetworkId) -instance MonadLog m => MonadLog (MonadBlockchainCardanoNodeT m) where +instance MonadTrans (MonadBlockchainCardanoNodeT e) where + lift = MonadBlockchainCardanoNodeT . lift .lift + +instance (MonadLog m) => MonadLog (MonadBlockchainCardanoNodeT e m) where logInfo' = lift . logInfo' logWarn' = lift . logWarn' logDebug' = lift . logDebug' -instance (MonadLog m, MonadFail m) => MonadFail (MonadBlockchainCardanoNodeT m) where - fail s = MonadBlockchainCardanoNodeT $ do - logWarnS s - lift (fail s) +instance (MonadLog m) => MonadFail (MonadBlockchainCardanoNodeT e m) where + fail = MonadBlockchainCardanoNodeT . throwError . FailWith diff --git a/src/devnet/convex-devnet.cabal b/src/devnet/convex-devnet.cabal index 239c7855..fb3d3888 100644 --- a/src/devnet/convex-devnet.cabal +++ b/src/devnet/convex-devnet.cabal @@ -67,6 +67,7 @@ library , convex-coin-selection , mtl , prettyprinter + , transformers -- cardano-node deps. , cardano-api @@ -100,4 +101,4 @@ test-suite convex-devnet-test , cardano-api , mtl , text - , aeson \ No newline at end of file + , aeson diff --git a/src/devnet/lib/Convex/Devnet/Wallet.hs b/src/devnet/lib/Convex/Devnet/Wallet.hs index bfb7e8e1..9a549430 100644 --- a/src/devnet/lib/Convex/Devnet/Wallet.hs +++ b/src/devnet/lib/Convex/Devnet/Wallet.hs @@ -1,7 +1,12 @@ -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} {-| Glue code -} module Convex.Devnet.Wallet( @@ -17,31 +22,31 @@ module Convex.Devnet.Wallet( runningNodeBlockchain ) where -import Cardano.Api (AddressInEra, BabbageEra, BuildTx, - Lovelace, Tx, TxBodyContent) -import qualified Cardano.Api as C -import Control.Monad.IO.Class (MonadIO (..)) -import Control.Monad.Reader (ReaderT (..), ask, lift) -import Control.Tracer (Tracer, traceWith) -import qualified Convex.BuildTx as BuildTx -import Convex.Class (MonadBlockchain, - runMonadBlockchainCardanoNodeT, - sendTx) -import qualified Convex.CoinSelection as CoinSelection -import Convex.Devnet.CardanoNode (RunningNode (..)) -import qualified Convex.Devnet.NodeQueries as NodeQueries -import Convex.Devnet.Utils (keysFor) -import Convex.Lenses (emptyTx) -import Convex.MonadLog (MonadLog (..)) -import Convex.Utxos (UtxoSet) -import qualified Convex.Utxos as Utxos -import Convex.Wallet (Wallet (..), address) -import qualified Convex.Wallet as Wallet -import Data.Aeson (FromJSON, ToJSON) -import Data.Function ((&)) -import Data.Text (Text) -import GHC.Generics (Generic) -import Prettyprinter (defaultLayoutOptions, layoutPretty) +import Cardano.Api (AddressInEra, BabbageEra, BuildTx, + Lovelace, Tx, TxBodyContent) +import qualified Cardano.Api as C +import Control.Monad.IO.Class (MonadIO (..)) +import Control.Monad.Reader (ReaderT (..), ask, lift) +import Control.Tracer (Tracer, traceWith) +import qualified Convex.BuildTx as BuildTx +import Convex.Class (MonadBlockchain, + runMonadBlockchainCardanoNodeT, + sendTx) +import qualified Convex.CoinSelection as CoinSelection +import Convex.Devnet.CardanoNode (RunningNode (..)) +import qualified Convex.Devnet.NodeQueries as NodeQueries +import Convex.Devnet.Utils (keysFor) +import Convex.Lenses (emptyTx) +import Convex.MonadLog (MonadLog (..)) +import Convex.Utxos (UtxoSet) +import qualified Convex.Utxos as Utxos +import Convex.Wallet (Wallet (..), address) +import qualified Convex.Wallet as Wallet +import Data.Aeson (FromJSON, ToJSON) +import Data.Function ((&)) +import Data.Text (Text) +import GHC.Generics (Generic) +import Prettyprinter (defaultLayoutOptions, layoutPretty) import qualified Prettyprinter.Render.Text as Render faucet :: IO Wallet @@ -73,17 +78,23 @@ createSeededWallet tracer node@RunningNode{rnNetworkId, rnNodeSocket} amount = d {-| Run a 'MonadBlockchain' action, using the @Tracer@ for log messages and the @RunningNode@ for blockchain stuff -} -runningNodeBlockchain :: Tracer IO WalletLog -> RunningNode -> (forall m. (MonadFail m, MonadLog m, MonadBlockchain m) => m a) -> IO a -runningNodeBlockchain tracer RunningNode{rnNodeSocket, rnNetworkId} = +runningNodeBlockchain :: + forall e a. (Show e) + => Tracer IO WalletLog + -> RunningNode + -> (forall m. (MonadFail m, MonadLog m, MonadBlockchain m) => m a) + -> IO a +runningNodeBlockchain tracer RunningNode{rnNodeSocket, rnNetworkId} h = let info = NodeQueries.localNodeConnectInfo rnNetworkId rnNodeSocket - in runTracerMonadLogT tracer . runMonadBlockchainCardanoNodeT info + in runTracerMonadLogT tracer $ do + runMonadBlockchainCardanoNodeT @e info h >>= either (fail . show) pure {-| Balance and submit the transaction using the wallet's UTXOs -} balanceAndSubmit :: Tracer IO WalletLog -> RunningNode -> Wallet -> TxBodyContent BuildTx BabbageEra -> IO (Tx BabbageEra) balanceAndSubmit tracer node wallet tx = do utxos <- walletUtxos node wallet - runningNodeBlockchain tracer node $ do + runningNodeBlockchain @String tracer node $ do (tx', _) <- CoinSelection.balanceForWallet wallet utxos tx _ <- sendTx tx' pure tx' diff --git a/src/trading-bot/lib/Convex/TradingBot/NodeClient/OrderClient.hs b/src/trading-bot/lib/Convex/TradingBot/NodeClient/OrderClient.hs index 5d3ca878..d75c516d 100644 --- a/src/trading-bot/lib/Convex/TradingBot/NodeClient/OrderClient.hs +++ b/src/trading-bot/lib/Convex/TradingBot/NodeClient/OrderClient.hs @@ -1,6 +1,8 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} {-| A wallet client for executing buy and sell orders -} @@ -66,13 +68,14 @@ applyBlock info logEnv ns wallet tx (catchingUp -> isCatchingUp) state block = K newState = apply state change when (not isCatchingUp) $ do - void $ runMonadBlockchainCardanoNodeT info $ do - (tx_, change_) <- balanceForWallet wallet (toUtxoTx state) tx - logInfoS (show tx_) - logInfoS (show change_) - sendTx tx_ + let action = + runMonadBlockchainCardanoNodeT @String info $ do + (tx_, change_) <- balanceForWallet wallet (toUtxoTx state) tx + logInfoS (show tx_) + logInfoS (show change_) + sendTx tx_ + void $ action >>= either (fail . show) pure empty - pure newState convBuyOrder :: Order 'Typed -> T.BuyOrder