diff --git a/src/base/lib/Convex/NodeQueries.hs b/src/base/lib/Convex/NodeQueries.hs index 7bf9d32..a191c78 100644 --- a/src/base/lib/Convex/NodeQueries.hs +++ b/src/base/lib/Convex/NodeQueries.hs @@ -1,19 +1,51 @@ -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ViewPatterns #-} {-| Conveniences for working with a local @cardano-node@ -} module Convex.NodeQueries( + -- * Connecting to the node loadConnectInfo, + localNodeConnectInfo, + + -- * Era-independent queries + queryEra, queryEraHistory, querySystemStart, - queryLocalState, + queryChainPoint, + queryTipBlock, queryTip, + queryTipSlotNo, + + -- * Era-specific queries + EraQuery(..), + QueryException(..), + TimeException(..), + queryInSupportedEra, + runEraQuery, + + queryEpoch, + queryLocalState, queryProtocolParameters, queryStakePools, - queryStakeAddresses + queryStakeAddresses, + queryUTxOFilter, + + -- ** Debug queries + queryUTxOByAddress, + queryUTxOWhole, + waitForTxIn, + waitForTx, + waitForTxInSpend ) where -import Cardano.Api (ChainPoint, +import Cardano.Api (AnyCardanoEra, + BlockNo, + ChainPoint, ConsensusModeParams (..), Env (..), EpochSlots (..), @@ -23,9 +55,13 @@ import Cardano.Api (ChainPoint, NetworkId (Mainnet, Testnet), NetworkMagic (..), Quantity, + SlotNo, SystemStart, + Tx, TxIn, envSecurityParam) import qualified Cardano.Api as CAPI +import Cardano.Api.Experimental (Era) +import qualified Cardano.Api.Experimental as X import Cardano.Api.Shelley (PoolId, StakeAddress, StakeCredential) @@ -33,21 +69,52 @@ import qualified Cardano.Chain.Genesis import Cardano.Crypto (RequiresNetworkMagic (..), getProtocolMagic) import Cardano.Ledger.Core (PParams) +import Cardano.Slotting.Slot (WithOrigin) +import Cardano.Slotting.Time (SlotLength) +import Control.Concurrent (threadDelay) +import Control.Exception (Exception, + throwIO) +import Control.Monad (unless, + when) import Control.Monad.Except (MonadError, throwError) import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Trans.Except (runExceptT) +import Convex.Utils (txnUtxos) +import Convex.Utxos (UtxoSet) +import qualified Convex.Utxos as Utxos import Data.Bifunctor (Bifunctor (..)) import Data.Map (Map) import Data.Set (Set) +import qualified Data.Set as Set import Data.SOP.Strict (NP ((:*))) +import Data.Word (Word64) import qualified Ouroboros.Consensus.Cardano.CanHardFork as Consensus import qualified Ouroboros.Consensus.HardFork.Combinator as Consensus +import Ouroboros.Consensus.HardFork.Combinator.AcrossEras (EraMismatch) import qualified Ouroboros.Consensus.HardFork.Combinator.AcrossEras as HFC import qualified Ouroboros.Consensus.HardFork.Combinator.Basics as HFC +import Ouroboros.Consensus.HardFork.History (interpretQuery, + slotToSlotLength) import Ouroboros.Consensus.Shelley.Eras (StandardConway) import qualified Ouroboros.Network.Protocol.LocalStateQuery.Type as T +-- | Exceptions thrown while querying the cardano node +data QueryException + = QueryAcquireException String -- ^ Failed to connect to the node + | QueryEraMismatchException EraMismatch -- ^ Mismatch between node era and query era + | QueryEraNotSupported CAPI.AnyCardanoEra -- ^ Attempting to query the node in an era that is not one of the supported eras + deriving (Eq, Show) + +instance Exception QueryException + +data TimeException = + TimePastHorizonException Consensus.PastHorizonException + | ChainPointAtGenesisFailure + deriving stock (Show) + +instance Exception TimeException + {-| Load the node config file and create 'LocalNodeConnectInfo' and 'Env' values that can be used to talk to the node. -} loadConnectInfo :: @@ -76,18 +143,38 @@ loadConnectInfo nodeConfigFilePath socketPath = do RequiresNoMagic -> Mainnet RequiresMagic -> Testnet (NetworkMagic networkMagic) - cardanoModeParams = CardanoModeParams . EpochSlots $ 10 * envSecurityParam env + localConsensusModeParams = CardanoModeParams . EpochSlots $ 10 * envSecurityParam env -- Connect to the node. let connectInfo :: LocalNodeConnectInfo connectInfo = LocalNodeConnectInfo { - localConsensusModeParams = cardanoModeParams, + localConsensusModeParams, localNodeNetworkId = networkId, localNodeSocketPath = CAPI.File socketPath } pure (connectInfo, env) +{-| 'LocalNodeConnectInfo' for a network ID and a socket path, + assuming default values for the 'CAPI.ConsensusModeParams'. + Cf. 'loadConnectInfo' which constructs the 'LocalNodeConnectInfo' + based on a node configuration file +-} +localNodeConnectInfo :: NetworkId -> FilePath -> LocalNodeConnectInfo +localNodeConnectInfo localNodeNetworkId (CAPI.File -> localNodeSocketPath) = + LocalNodeConnectInfo + { localConsensusModeParams = cardanoModeParams + , localNodeNetworkId + , localNodeSocketPath + } + +cardanoModeParams :: CAPI.ConsensusModeParams +cardanoModeParams = CAPI.CardanoModeParams $ CAPI.EpochSlots defaultByronEpochSlots + where + -- NOTE(AB): extracted from Parsers in cardano-cli, this is needed to run in 'cardanoMode' which + -- is the default for cardano-cli + defaultByronEpochSlots = 21600 :: Word64 + -- | Get the system start from the local cardano node querySystemStart :: LocalNodeConnectInfo -> IO SystemStart querySystemStart = queryLocalState CAPI.QuerySystemStart @@ -96,43 +183,147 @@ querySystemStart = queryLocalState CAPI.QuerySystemStart queryEraHistory :: LocalNodeConnectInfo -> IO EraHistory queryEraHistory = queryLocalState CAPI.QueryEraHistory --- | Get the tip from the local cardano node -queryTip :: LocalNodeConnectInfo -> IO ChainPoint -queryTip = queryLocalState CAPI.QueryChainPoint +-- | Get the chain point from the local cardano node +queryChainPoint :: LocalNodeConnectInfo -> IO ChainPoint +queryChainPoint = queryLocalState CAPI.QueryChainPoint -queryStakePools :: LocalNodeConnectInfo -> IO (Set PoolId) -queryStakePools connectInfo = do - result <- queryLocalState - (CAPI.QueryInEra (CAPI.QueryInShelleyBasedEra CAPI.ShelleyBasedEraConway CAPI.QueryStakePools)) - connectInfo - case result of - Left err -> do - fail ("queryStakePools: failed with: " <> show err) - Right k -> pure k +-- | Get the tip (slot no and block hash) as well as the length of the current slot +-- Throws at least 'TimeException' if the time conversion fails and 'QueryException' +-- if the node query fails. +queryTip :: LocalNodeConnectInfo -> IO (SlotNo, SlotLength, CAPI.Hash CAPI.BlockHeader) +queryTip connectInfo = queryChainPoint connectInfo >>= \case + CAPI.ChainPointAtGenesis -> throwIO ChainPointAtGenesisFailure + CAPI.ChainPoint slot hsh -> do + sl <- querySlotLength connectInfo slot + pure (slot, sl, hsh) -queryStakeAddresses :: LocalNodeConnectInfo -> Set StakeCredential -> NetworkId -> IO (Map StakeAddress Quantity, Map StakeAddress PoolId) -queryStakeAddresses info creds nid = do - result <- queryLocalState - (CAPI.QueryInEra (CAPI.QueryInShelleyBasedEra CAPI.ShelleyBasedEraConway (CAPI.QueryStakeAddresses creds nid))) - info - case result of - Left err -> do - fail ("queryStakeAddresses: failed with: " <> show err) - Right k -> pure (first (fmap CAPI.lovelaceToQuantity) k) +-- | Get the tip (slot no and block hash) +-- Throws at least 'TimeException' if the time conversion fails and 'QueryException' +-- if the node query fails. +queryTipSlotNo :: LocalNodeConnectInfo -> IO (SlotNo, SlotLength) +queryTipSlotNo = fmap (\(s, l, _) -> (s, l)) . queryTip + +querySlotLength :: LocalNodeConnectInfo -> SlotNo -> IO SlotLength +querySlotLength connectInfo slotNo = do + CAPI.EraHistory interpreter <- queryEraHistory connectInfo + case interpretQuery interpreter (slotToSlotLength slotNo) of + Left err -> throwIO $ TimePastHorizonException err + Right slength -> pure $ slength + +-- | Get the block number from the local cardano node +queryTipBlock :: LocalNodeConnectInfo -> IO (WithOrigin BlockNo) +queryTipBlock = queryLocalState CAPI.QueryChainBlockNo + +-- | Get the node's era from the local cardano node +queryEra :: LocalNodeConnectInfo -> IO AnyCardanoEra +queryEra = queryLocalState CAPI.QueryCurrentEra -- | Run a local state query on the local cardano node, using the volatile tip +-- Throws 'QueryException' if connection to the node cannot be acquired queryLocalState :: CAPI.QueryInMode b -> LocalNodeConnectInfo -> IO b queryLocalState query connectInfo = do runExceptT (CAPI.queryNodeLocalState connectInfo T.VolatileTip query) >>= \case Left err -> do - fail ("queryLocalState: Failed with " <> show err) + throwIO $ QueryAcquireException $ show err Right result -> pure result --- | Get the protocol parameters from the local cardano node +-- TODO: Add missing queries from Convex.Devnet.NodeQueries + +{-| Era-specific query with an era-independent result +-} +data EraQuery era result = + forall eraResult. EraQuery + { eqQuery :: CAPI.QueryInShelleyBasedEra era eraResult + , eqResult :: eraResult -> result + } + +{-| Run an 'EraQuery', throwing 'QueryException' if the node's era does not match the query era +-} +runEraQuery :: CAPI.IsShelleyBasedEra era => LocalNodeConnectInfo -> EraQuery era result -> IO result +runEraQuery connectInfo EraQuery{eqQuery, eqResult} = + queryLocalState (CAPI.QueryInEra $ CAPI.QueryInShelleyBasedEra CAPI.shelleyBasedEra eqQuery) connectInfo >>= \case + Left eraMismatch -> throwIO (QueryEraMismatchException eraMismatch) + Right x -> pure (eqResult x) + +-- | Query the node in one of the supported eras. +-- Throws 'QueryException' if connection to the node cannot be acquired, +-- if the node's era is not one of the supported eras, or if the node's +-- era changes between us asking for the era and sending the era-specific query +queryInSupportedEra :: LocalNodeConnectInfo -> (forall era. Era era -> EraQuery era result) -> IO result +queryInSupportedEra connectInfo qry = do + queryEra connectInfo >>= \case + CAPI.AnyCardanoEra CAPI.BabbageEra -> runEraQuery connectInfo (qry X.BabbageEra) + CAPI.AnyCardanoEra CAPI.ConwayEra -> runEraQuery connectInfo (qry X.ConwayEra) + era -> throwIO (QueryEraNotSupported era) + +-- | Get the conway protocol parameters from the local cardano node +-- Throws 'QueryException' if the node's era is not conway or if the connection +-- to the node cannot be acquired queryProtocolParameters :: LocalNodeConnectInfo -> IO (PParams StandardConway) -queryProtocolParameters connectInfo = do - result <- queryLocalState (CAPI.QueryInEra (CAPI.QueryInShelleyBasedEra CAPI.ShelleyBasedEraConway CAPI.QueryProtocolParameters)) connectInfo - case result of - Left err -> do - fail ("queryProtocolParameters: failed with: " <> show err) - Right x -> pure x +queryProtocolParameters connectInfo = runEraQuery connectInfo $ + EraQuery{eqQuery = CAPI.QueryProtocolParameters, eqResult = id} + +-- | Get the stake and the IDs of the stake pool for a set of stake credentials +-- Throws 'QueryException' if the node's era is not supported or if the connection +-- to the node cannot be acquired +queryStakeAddresses :: LocalNodeConnectInfo -> Set StakeCredential -> NetworkId -> IO (Map StakeAddress Quantity, Map StakeAddress PoolId) +queryStakeAddresses info creds nid = do + queryInSupportedEra info $ \case + X.BabbageEra -> EraQuery{eqQuery = CAPI.QueryStakeAddresses creds nid, eqResult = first (fmap CAPI.lovelaceToQuantity)} + X.ConwayEra -> EraQuery{eqQuery = CAPI.QueryStakeAddresses creds nid, eqResult = first (fmap CAPI.lovelaceToQuantity)} + +-- | Get the set of registered stake pools +-- Throws 'QueryException' if the node's era is not supported or if the connection +-- to the node cannot be acquired +queryStakePools :: LocalNodeConnectInfo -> IO (Set PoolId) +queryStakePools connectInfo = queryInSupportedEra connectInfo $ \case + X.BabbageEra -> EraQuery{eqQuery = CAPI.QueryStakePools, eqResult = id} + X.ConwayEra -> EraQuery{eqQuery = CAPI.QueryStakePools, eqResult = id} + +-- | Get the current epoch +-- Throws 'QueryException' if the node's era is not supported or if the connection +-- to the node cannot be acquired +queryEpoch :: LocalNodeConnectInfo -> IO CAPI.EpochNo +queryEpoch connectInfo = queryInSupportedEra connectInfo $ \case + X.BabbageEra -> EraQuery{eqQuery = CAPI.QueryEpoch, eqResult = id} + X.ConwayEra -> EraQuery{eqQuery = CAPI.QueryEpoch, eqResult = id} + +-- | Query UTxO for all given addresses at given point. +-- Throws 'QueryException' if the node's era is not supported or if the connection +-- to the node cannot be acquired +queryUTxOFilter :: LocalNodeConnectInfo -> CAPI.QueryUTxOFilter -> IO (UtxoSet CAPI.CtxUTxO ()) +queryUTxOFilter connectInfo flt = queryInSupportedEra connectInfo $ \case + X.BabbageEra -> EraQuery{eqQuery = CAPI.QueryUTxO flt, eqResult = Utxos.fromApiUtxo } + X.ConwayEra -> EraQuery{eqQuery = CAPI.QueryUTxO flt, eqResult = Utxos.fromApiUtxo } + +queryUTxOByAddress :: LocalNodeConnectInfo -> [CAPI.AddressAny] -> IO (UtxoSet CAPI.CtxUTxO ()) +queryUTxOByAddress connectInfo addresses = + queryUTxOFilter connectInfo $ CAPI.QueryUTxOByAddress $ Set.fromList addresses + +queryUTxOWhole :: LocalNodeConnectInfo -> IO (UtxoSet CAPI.CtxUTxO ()) +queryUTxOWhole connectInfo = queryUTxOFilter connectInfo CAPI.QueryUTxOWhole + +{-| Wait until the output appears on the chain +-} +waitForTxIn :: LocalNodeConnectInfo -> TxIn -> IO () +waitForTxIn connectInfo txIn = go where + go = do + utxo <- queryUTxOFilter connectInfo (CAPI.QueryUTxOByTxIn (Set.singleton txIn)) + when (utxo == mempty) $ do + threadDelay 2_000_000 + go + +{-| Wait until the first output of the transaction appears on the chain +-} +waitForTx :: forall era. LocalNodeConnectInfo -> Tx era -> IO () +waitForTx connectInfo tx = waitForTxIn connectInfo (fst $ head $ txnUtxos tx) + +{-| Wait until the output disappears from the chain +-} +waitForTxInSpend :: LocalNodeConnectInfo -> TxIn -> IO () +waitForTxInSpend connectInfo txIn = go where + go = do + utxo <- queryUTxOFilter connectInfo (CAPI.QueryUTxOByTxIn (Set.singleton txIn)) + unless (utxo == mempty) $ do + threadDelay 2_000_000 + go diff --git a/src/devnet/convex-devnet.cabal b/src/devnet/convex-devnet.cabal index 821bf20..02f0b8c 100644 --- a/src/devnet/convex-devnet.cabal +++ b/src/devnet/convex-devnet.cabal @@ -45,7 +45,6 @@ library Convex.Devnet.CardanoNode Convex.Devnet.CardanoNode.Types Convex.Devnet.Logging - Convex.Devnet.NodeQueries Convex.Devnet.Utils Convex.Devnet.Wallet Convex.Devnet.WalletServer @@ -64,8 +63,6 @@ library , temporary , async , unix - , exceptions - , containers , convex-wallet , convex-base , convex-optics @@ -86,9 +83,7 @@ library , cardano-slotting , cardano-ledger-conway , io-classes - , ouroboros-consensus , ouroboros-consensus-cardano - , ouroboros-network-protocols -- logging , contra-tracer diff --git a/src/devnet/lib/Convex/Devnet/CardanoNode.hs b/src/devnet/lib/Convex/Devnet/CardanoNode.hs index 4e9f01b..5c95c99 100644 --- a/src/devnet/lib/Convex/Devnet/CardanoNode.hs +++ b/src/devnet/lib/Convex/Devnet/CardanoNode.hs @@ -58,11 +58,11 @@ import Convex.Devnet.CardanoNode.Types (GenesisConfigChanges (..), RunningStakePoolNode (..), StakePoolNodeParams (..), defaultPortsConfig) -import qualified Convex.Devnet.NodeQueries as Q import Convex.Devnet.Utils (checkProcessHasNotDied, defaultNetworkId, failure, readConfigFile, withLogFile) import qualified Convex.Devnet.Wallet as W +import qualified Convex.NodeQueries as Q import Convex.Wallet (Wallet, paymentCredential) import Data.Aeson (FromJSON, ToJSON (toJSON), (.=)) @@ -240,13 +240,14 @@ waitForFullySynchronized :: RunningNode -> IO () waitForFullySynchronized tracer RunningNode{rnNodeSocket, rnNetworkId} = do - systemStart <- Q.querySystemStart rnNetworkId rnNodeSocket + let info = Q.localNodeConnectInfo rnNetworkId rnNodeSocket + systemStart <- Q.querySystemStart info check systemStart where check systemStart = do targetTime <- toRelativeTime systemStart <$> getCurrentTime - eraHistory <- Q.queryEraHistory rnNetworkId rnNodeSocket - (tipSlotNo, _slotLength) <- Q.queryTipSlotNo rnNetworkId rnNodeSocket + eraHistory <- Q.queryEraHistory (Q.localNodeConnectInfo rnNetworkId rnNodeSocket) + (tipSlotNo, _slotLength) <- Q.queryTipSlotNo (Q.localNodeConnectInfo rnNetworkId rnNodeSocket) (tipTime, _slotLength) <- either throwIO pure $ C.getProgress tipSlotNo eraHistory let timeDifference = diffRelativeTime targetTime tipTime let percentDone = realToFrac (100.0 * getRelativeTime tipTime / getRelativeTime targetTime) @@ -259,7 +260,7 @@ waitForFullySynchronized tracer RunningNode{rnNodeSocket, rnNetworkId} = do -} waitForBlock :: RunningNode -> IO C.BlockNo waitForBlock n@RunningNode{rnNodeSocket, rnNetworkId} = do - withOriginToMaybe <$> Q.queryTipBlock rnNetworkId rnNodeSocket >>= \case + withOriginToMaybe <$> Q.queryTipBlock (Q.localNodeConnectInfo rnNetworkId rnNodeSocket) >>= \case Just blockNo | blockNo >= 1 -> pure blockNo _ -> do threadDelay 1_000_000 >> waitForBlock n @@ -278,12 +279,12 @@ waitForNextBlock' node blockNo = do waitForNextEpoch :: RunningNode -> IO C.EpochNo waitForNextEpoch n@RunningNode{rnNodeSocket, rnNetworkId} = do - currentEpochNo <- Q.queryEpoch rnNetworkId rnNodeSocket + currentEpochNo <- Q.queryEpoch (Q.localNodeConnectInfo rnNetworkId rnNodeSocket) waitForNextEpoch' n currentEpochNo waitForNextEpoch' :: RunningNode -> C.EpochNo -> IO C.EpochNo waitForNextEpoch' n@RunningNode{rnNodeSocket, rnNetworkId} epochNo = do - currentEpochNo <- Q.queryEpoch rnNetworkId rnNodeSocket + currentEpochNo <- Q.queryEpoch (Q.localNodeConnectInfo rnNetworkId rnNodeSocket) if currentEpochNo > epochNo then pure currentEpochNo else threadDelay 1_000_000 >> waitForNextEpoch' n epochNo @@ -500,7 +501,7 @@ withCardanoStakePoolNodeDevnetConfig tracer stateDirectory wallet params nodeCon kesKey <- C.generateSigningKey C.AsKesKey stakePoolKey <- C.generateSigningKey C.AsStakePoolKey - C.SlotNo slotNo <- fst <$> Q.queryTipSlotNo rnNetworkId rnNodeSocket + C.SlotNo slotNo <- fst <$> Q.queryTipSlotNo (Q.localNodeConnectInfo rnNetworkId rnNodeSocket) let minDeposit = 500_000_000 diff --git a/src/devnet/lib/Convex/Devnet/NodeQueries.hs b/src/devnet/lib/Convex/Devnet/NodeQueries.hs deleted file mode 100644 index ace94f6..0000000 --- a/src/devnet/lib/Convex/Devnet/NodeQueries.hs +++ /dev/null @@ -1,223 +0,0 @@ -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE ViewPatterns #-} -{-| Helper functions for querying a local @cardano-node@ using the socket interface --} -module Convex.Devnet.NodeQueries( - querySystemStart, - queryEpoch, - queryEraHistory, - queryTip, - queryTipBlock, - queryTipSlotNo, - queryUTxO, - queryUTxOWhole, - waitForTxn, - waitForTxIn, - waitForTxInSpend, - localNodeConnectInfo, - loadConnectInfo, - queryEra -) where - -import Cardano.Api (Address, - BlockNo, - EraHistory (..), - LocalNodeConnectInfo (..), - NetworkId, - QueryInMode, - QueryUTxOFilter, - ShelleyAddr, - SlotNo, Tx, - TxIn, UTxO) -import qualified Cardano.Api as C -import Cardano.Slotting.Slot (WithOrigin) -import Cardano.Slotting.Time (SlotLength, - SystemStart) -import Control.Concurrent (threadDelay) -import Control.Exception (Exception, - throwIO) -import Control.Monad (unless, - when) -import Control.Monad.Catch (MonadThrow) -import Control.Monad.Except (runExceptT) -import Control.Monad.IO.Class (MonadIO (..)) -import Convex.Devnet.Utils (failure) -import Convex.NodeQueries (loadConnectInfo) -import Convex.Utils (txnUtxos) -import qualified Convex.Utxos as Utxos -import qualified Data.Set as Set -import Data.Word (Word64) -import Ouroboros.Consensus.HardFork.Combinator.AcrossEras (EraMismatch) -import Ouroboros.Consensus.HardFork.History (interpretQuery, - slotToSlotLength) -import qualified Ouroboros.Network.Protocol.LocalStateQuery.Type as T -import Prelude - -data QueryException - = QueryAcquireException String - | QueryEraMismatchException EraMismatch - deriving (Eq, Show) - -instance Exception QueryException - --- | Get the 'SystemStart' from the node -querySystemStart :: - NetworkId -> - -- ^ network Id to use for node query - FilePath -> - -- ^ Node socket - IO SystemStart -querySystemStart = queryLocalState C.QuerySystemStart - --- | Get the 'EraHistory' from the node -queryEraHistory :: - NetworkId -> - -- ^ network Id to use for node query - FilePath -> - -- ^ Node socket - IO EraHistory -queryEraHistory = queryLocalState C.QueryEraHistory - -queryLocalState :: QueryInMode b -> NetworkId -> FilePath -> IO b -queryLocalState query networkId socket = do - runExceptT (C.queryNodeLocalState (localNodeConnectInfo networkId socket) T.VolatileTip query) >>= \case - Left err -> do - failure $ "querySystemStart: Failed with " <> show err - Right result -> pure result - - -localNodeConnectInfo :: NetworkId -> FilePath -> C.LocalNodeConnectInfo -localNodeConnectInfo localNodeNetworkId (C.File -> localNodeSocketPath) = - C.LocalNodeConnectInfo - { localConsensusModeParams = cardanoModeParams - , localNodeNetworkId - , localNodeSocketPath - } - -cardanoModeParams :: C.ConsensusModeParams -cardanoModeParams = C.CardanoModeParams $ C.EpochSlots defaultByronEpochSlots - where - -- NOTE(AB): extracted from Parsers in cardano-cli, this is needed to run in 'cardanoMode' which - -- is the default for cardano-cli - defaultByronEpochSlots = 21600 :: Word64 - -queryTipBlock :: NetworkId -> FilePath -> IO (WithOrigin BlockNo) -queryTipBlock = queryLocalState C.QueryChainBlockNo - -queryEra :: NetworkId -> FilePath -> IO C.AnyCardanoEra -queryEra = queryLocalState C.QueryCurrentEra - -queryEpoch :: NetworkId -> FilePath -> IO C.EpochNo -queryEpoch nid path = do - result <- queryLocalState - (C.QueryInEra - (C.QueryInShelleyBasedEra C.ShelleyBasedEraConway C.QueryEpoch) - ) - nid path - case result of - Left err -> do - fail ("queryEpoch: failed with: " <> show err) - Right k -> pure k - --- | Get the tip (slot no. and block hash) from the node -queryTip :: - NetworkId -> - -- ^ network Id to use for node query - FilePath -> - -- ^ Node socket - IO (SlotNo, SlotLength, C.Hash C.BlockHeader) -queryTip networkId socket = queryLocalState C.QueryChainPoint networkId socket >>= \case - C.ChainPointAtGenesis -> failure "queryTip: chain point at genesis" - C.ChainPoint slot hsh -> getSlotLength slot >>= (\i -> pure (slot, i, hsh)) - - where - getSlotLength :: SlotNo -> IO SlotLength - getSlotLength slotNo = do - (EraHistory interpreter) <- queryEraHistory networkId socket - case interpretQuery interpreter (slotToSlotLength slotNo) of - Left err -> failure $ "queryTip: Failed with " <> show err - Right slength -> pure $ slength - --- | Get the slot no of the current tip from the node -queryTipSlotNo :: - NetworkId -> - -- ^ network Id to use for node query - FilePath -> - -- ^ Node socket - IO (SlotNo, SlotLength) -queryTipSlotNo networkId socket = queryTip networkId socket >>= (\(s, l, _) -> pure (s, l)) - --- | Query UTxO for all given addresses at given point. --- --- Throws at least 'QueryException' if query fails. -queryUTxOFilter :: forall era. C.IsShelleyBasedEra era => NetworkId -> FilePath -> QueryUTxOFilter -> IO (UTxO era) -queryUTxOFilter networkId socket flt = - let query = - C.QueryInEra - ( C.QueryInShelleyBasedEra - C.shelleyBasedEra - ( C.QueryUTxO flt) - ) - in queryLocalState query networkId socket >>= throwOnEraMismatch - --- | Query UTxO for all given addresses at given point. --- --- Throws at least 'QueryException' if query fails. -queryUTxO :: forall era. C.IsShelleyBasedEra era => NetworkId -> FilePath -> [Address ShelleyAddr] -> IO (UTxO era) -queryUTxO networkId socket addresses = - queryUTxOFilter networkId socket (C.QueryUTxOByAddress (Set.fromList $ map C.AddressShelley addresses)) - --- | Query the entire UTxO set --- --- Throws at least 'QueryException' if query fails. -queryUTxOWhole :: NetworkId -> FilePath -> IO (UTxO C.ConwayEra) -queryUTxOWhole networkId socket = queryUTxOFilter networkId socket C.QueryUTxOWhole - -throwOnEraMismatch :: (MonadThrow m, MonadIO m) => Either EraMismatch a -> m a -throwOnEraMismatch res = - case res of - Left eraMismatch -> liftIO $ throwIO $ QueryEraMismatchException eraMismatch - Right result -> pure result - -{-| Wait until the output appears on the chain --} -waitForTxIn :: NetworkId -> FilePath -> TxIn -> IO () -waitForTxIn networkId socket txIn = do - let query = - C.QueryInEra - ( C.QueryInShelleyBasedEra - C.ShelleyBasedEraConway - ( C.QueryUTxO - (C.QueryUTxOByTxIn (Set.singleton txIn)) - ) - ) - go = do - utxo <- Utxos.fromApiUtxo <$> (queryLocalState query networkId socket >>= throwOnEraMismatch) - when (utxo == mempty) $ do - threadDelay 2_000_000 - go - go - -waitForTxn :: forall era. NetworkId -> FilePath -> Tx era -> IO () -waitForTxn network socket (head . fmap fst . txnUtxos -> txi) = waitForTxIn network socket txi - -{-| Wait until the 'TxIn' is not part of the utxo set anymore --} -waitForTxInSpend :: NetworkId -> FilePath -> TxIn -> IO () -waitForTxInSpend networkId socket txIn = do - let query = - C.QueryInEra - ( C.QueryInShelleyBasedEra - C.ShelleyBasedEraConway - ( C.QueryUTxO - (C.QueryUTxOByTxIn (Set.singleton txIn)) - ) - ) - go = do - utxo <- Utxos.fromApiUtxo <$> (queryLocalState query networkId socket >>= throwOnEraMismatch) - unless (utxo == mempty) $ do - threadDelay 2_000_000 - go - go diff --git a/src/devnet/lib/Convex/Devnet/Wallet.hs b/src/devnet/lib/Convex/Devnet/Wallet.hs index 476af13..43c07f9 100644 --- a/src/devnet/lib/Convex/Devnet/Wallet.hs +++ b/src/devnet/lib/Convex/Devnet/Wallet.hs @@ -39,12 +39,11 @@ import Convex.Class (MonadBlockchain (queryNetworkI import Convex.CoinSelection (ChangeOutputPosition (TrailingChange)) import qualified Convex.CoinSelection as CoinSelection import Convex.Devnet.CardanoNode.Types (RunningNode (..)) -import qualified Convex.Devnet.NodeQueries as NodeQueries import Convex.Devnet.Utils (keysFor) import Convex.MonadLog (MonadLog (..)) +import qualified Convex.NodeQueries as NodeQueries import Convex.Utils (failOnError) 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) @@ -61,7 +60,7 @@ faucet = Wallet . snd <$> keysFor "faucet" -} walletUtxos :: RunningNode -> Wallet -> IO (UtxoSet C.CtxUTxO ()) walletUtxos RunningNode{rnNodeSocket, rnNetworkId} wllt = - Utxos.fromApiUtxo <$> NodeQueries.queryUTxO @C.ConwayEra rnNetworkId rnNodeSocket [address rnNetworkId wllt] + NodeQueries.queryUTxOByAddress (NodeQueries.localNodeConnectInfo rnNetworkId rnNodeSocket) [C.toAddressAny $ address rnNetworkId wllt] {-| Send @n@ times the given amount of lovelace to the address -} @@ -77,7 +76,7 @@ createSeededWallet :: Tracer IO WalletLog -> RunningNode -> Int -> Quantity -> I createSeededWallet tracer node@RunningNode{rnNetworkId, rnNodeSocket} n amount = do wallet <- Wallet.generateWallet traceWith tracer (GeneratedWallet wallet) - sendFaucetFundsTo tracer node (Wallet.addressInEra rnNetworkId wallet) n amount >>= NodeQueries.waitForTxn rnNetworkId rnNodeSocket + sendFaucetFundsTo tracer node (Wallet.addressInEra rnNetworkId wallet) n amount >>= NodeQueries.waitForTx (NodeQueries.localNodeConnectInfo rnNetworkId rnNodeSocket) pure wallet {-| Run a 'MonadBlockchain' action, using the @Tracer@ for log messages and the diff --git a/src/devnet/lib/Convex/Devnet/WalletServer.hs b/src/devnet/lib/Convex/Devnet/WalletServer.hs index bea468d..45e0b02 100644 --- a/src/devnet/lib/Convex/Devnet/WalletServer.hs +++ b/src/devnet/lib/Convex/Devnet/WalletServer.hs @@ -20,9 +20,9 @@ import qualified Cardano.Api as C import Control.Concurrent (threadDelay) import Control.Tracer (Tracer, contramap, traceWith) import Convex.Devnet.CardanoNode.Types (RunningNode (..)) -import qualified Convex.Devnet.NodeQueries as NodeQueries import Convex.Devnet.Utils (failure, withLogFile) import qualified Convex.Devnet.Wallet as Wallet +import qualified Convex.NodeQueries as NodeQueries import Convex.Utxos (UtxoSet) import qualified Convex.Wallet.API as API import Convex.Wallet.Cli.Command (CliCommand (..)) @@ -103,7 +103,7 @@ withWallet tracer stateDirectory rn@RunningNode{rnNodeSocket, rnNodeConfigFile, , rwsManager , rwsClient } - _ <- sendFundsToOperator tracer rn op (C.Quantity 100_000_000) >>= NodeQueries.waitForTxn rnNetworkId rnNodeSocket + _ <- sendFundsToOperator tracer rn op (C.Quantity 100_000_000) >>= NodeQueries.waitForTx (NodeQueries.localNodeConnectInfo rnNetworkId rnNodeSocket) waitUntilAvailable tracer rws action rws diff --git a/src/devnet/test/Spec.hs b/src/devnet/test/Spec.hs index 154be4a..7ba72cc 100644 --- a/src/devnet/test/Spec.hs +++ b/src/devnet/test/Spec.hs @@ -31,17 +31,17 @@ import Convex.Devnet.CardanoNode.Types (GenesisConfigChanges (..), forkIntoConwayInEpoch) import Convex.Devnet.Logging (contramap, showLogsOnFailure, traceWith) -import Convex.Devnet.NodeQueries (loadConnectInfo) -import qualified Convex.Devnet.NodeQueries as Queries import Convex.Devnet.Utils (failAfter, failure, withTempDir) import Convex.Devnet.Wallet (WalletLog) import qualified Convex.Devnet.Wallet as W import Convex.Devnet.WalletServer (getUTxOs, withWallet) import qualified Convex.Devnet.WalletServer as WS -import Convex.NodeQueries (queryProtocolParameters, +import Convex.NodeQueries (loadConnectInfo, + queryProtocolParameters, queryStakeAddresses, queryStakePools) +import qualified Convex.NodeQueries as Queries import qualified Convex.Utxos as Utxos import Data.Aeson (FromJSON, ToJSON) import Data.List (isInfixOf) @@ -87,7 +87,7 @@ startLocalNode = do runExceptT (loadConnectInfo rnNodeConfigFile rnNodeSocket) >>= \case Left err -> failure (show err) Right{} -> do - Queries.queryEra rnNetworkId rnNodeSocket + Queries.queryEra (Queries.localNodeConnectInfo rnNetworkId rnNodeSocket) >>= assertBool "Should be in conway era" . (==) (C.anyCardanoEra C.ConwayEra) transitionToConway :: IO () @@ -96,7 +96,7 @@ transitionToConway = do failAfter 5 $ withTempDir "cardano-cluster" $ \tmp -> do withCardanoNodeDevnetConfig tr tmp (forkIntoConwayInEpoch 0) defaultPortsConfig $ \RunningNode{rnNetworkId, rnNodeSocket} -> do - Queries.queryEra rnNetworkId rnNodeSocket + Queries.queryEra (Queries.localNodeConnectInfo rnNetworkId rnNodeSocket) >>= assertBool "Should be in conway era" . (==) (C.anyCardanoEra C.ConwayEra) startLocalStakePoolNode :: IO () diff --git a/src/node-client/lib/Convex/NodeClient/WaitForTxnClient.hs b/src/node-client/lib/Convex/NodeClient/WaitForTxnClient.hs index 49cdf5a..68dd6f5 100644 --- a/src/node-client/lib/Convex/NodeClient/WaitForTxnClient.hs +++ b/src/node-client/lib/Convex/NodeClient/WaitForTxnClient.hs @@ -39,7 +39,7 @@ transaction. -} runWaitForTxn :: LocalNodeConnectInfo -> Env -> TxId -> IO (TMVar BlockInMode) runWaitForTxn connectInfo env txi = do - tip' <- NodeQueries.queryTip connectInfo + tip' <- NodeQueries.queryChainPoint connectInfo tmv <- newEmptyTMVarIO _ <- forkIO $ C.connectToLocalNode connectInfo (protocols $ waitForTxnClient tmv tip' txi env) pure tmv