Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw error on send tx failure #5

Merged
merged 9 commits into from
Jul 19, 2023
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
68 changes: 44 additions & 24 deletions src/base/lib/Convex/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
module Convex.Class(
MonadBlockchain(..),
MonadMockchain(..),
MonadBlockchainError(..),
getSlot,
setSlot,
setPOSIXTime,
Expand Down Expand Up @@ -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 (..),
Expand Down Expand Up @@ -188,39 +192,51 @@ 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)
case result of
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
Expand All @@ -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))))
Expand All @@ -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
3 changes: 2 additions & 1 deletion src/devnet/convex-devnet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ library
, convex-coin-selection
, mtl
, prettyprinter
, transformers

-- cardano-node deps.
, cardano-api
Expand Down Expand Up @@ -100,4 +101,4 @@ test-suite convex-devnet-test
, cardano-api
, mtl
, text
, aeson
, aeson
77 changes: 44 additions & 33 deletions src/devnet/lib/Convex/Devnet/Wallet.hs
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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'
Expand Down
15 changes: 9 additions & 6 deletions src/trading-bot/lib/Convex/TradingBot/NodeClient/OrderClient.hs
Original file line number Diff line number Diff line change
@@ -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
-}
Expand Down Expand Up @@ -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
Expand Down
Loading