diff --git a/fixtures/schemata/JsonWsp/UtxoQueryResponse.medea b/fixtures/schemata/JsonRpc2/UtxoQueryResponse.medea similarity index 73% rename from fixtures/schemata/JsonWsp/UtxoQueryResponse.medea rename to fixtures/schemata/JsonRpc2/UtxoQueryResponse.medea index a5af178af9..9e55c1a1cb 100644 --- a/fixtures/schemata/JsonWsp/UtxoQueryResponse.medea +++ b/fixtures/schemata/JsonRpc2/UtxoQueryResponse.medea @@ -2,42 +2,26 @@ $schema $start $type $object $properties - $property-name "type" - $property-schema jsonWspType - $property-name "version" - $property-schema versionType - $property-name "servicename" - $property-schema serviceType - $property-name "methodname" + $property-name "jsonrpc" + $property-schema jsonRpcType + $property-name "method" $property-schema methodType $property-name "result" $property-schema result - $property-name "reflection" + $property-name "id" $property-schema mirrorType -$schema jsonWspType +$schema jsonRpcType $type $string $string-values - "jsonwsp/response" - -$schema versionType - $type - $string - $string-values - "1.0" - -$schema serviceType - $type - $string - $string-values - "ogmios" + "2.0" $schema methodType $type $string $string-values - "Query" + "queryLedgerState/utxo" $schema mirrorType $type diff --git a/fixtures/test/parsing/JsonWsp/UtxoQueryResponse.json b/fixtures/test/parsing/JsonRpc2/UtxoQueryResponse.json similarity index 77% rename from fixtures/test/parsing/JsonWsp/UtxoQueryResponse.json rename to fixtures/test/parsing/JsonRpc2/UtxoQueryResponse.json index c8e52beeed..6442f327f9 100644 --- a/fixtures/test/parsing/JsonWsp/UtxoQueryResponse.json +++ b/fixtures/test/parsing/JsonRpc2/UtxoQueryResponse.json @@ -1,9 +1,7 @@ [ { - "type": "jsonwsp/response", - "version": "1.0", - "servicename": "ogmios", - "methodname": "Query", + "jsonrpc": "2.0", + "method": "queryLedgerState/utxo", "result": [ [ { @@ -19,6 +17,6 @@ } ] ], - "reflection": "Hello123" + "id": "Hello123" } ] diff --git a/src/Internal/Address.purs b/src/Internal/Address.purs index fd09718a46..746c8b23fe 100644 --- a/src/Internal/Address.purs +++ b/src/Internal/Address.purs @@ -32,12 +32,12 @@ import Data.Maybe (Maybe) -------------------------------------------------------------------------------- -- Conversion between various address types -------------------------------------------------------------------------------- --- JsonWsp.Address is a bech32 string, so wrap to Transaction.Types.Bech32 --- | Converts an `JsonWsp.Address` (bech32string) to internal `Address` +-- JsonRpc2.Address is a bech32 string, so wrap to Transaction.Types.Bech32 +-- | Converts an `JsonRpc2.Address` (bech32string) to internal `Address` ogmiosAddressToAddress :: Ogmios.OgmiosAddress -> Maybe Address ogmiosAddressToAddress = addressFromBech32 --- | Converts an (internal) `Address` to `JsonWsp.Address` (bech32string) +-- | Converts an (internal) `Address` to `JsonRpc2.Address` (bech32string) addressToOgmiosAddress :: Address -> Ogmios.OgmiosAddress addressToOgmiosAddress = addressBech32 diff --git a/src/Internal/QueryM.purs b/src/Internal/QueryM.purs index f5ef2c1b8b..12f9d7cdd7 100644 --- a/src/Internal/QueryM.purs +++ b/src/Internal/QueryM.purs @@ -119,7 +119,7 @@ import Ctl.Internal.QueryM.Dispatcher , newDispatcher , newPendingRequests ) -import Ctl.Internal.QueryM.JsonWsp as JsonWsp +import Ctl.Internal.QueryM.JsonRpc2 as JsonRpc2 import Ctl.Internal.QueryM.Ogmios ( AdditionalUtxoSet , DelegationsAndRewardsR @@ -301,8 +301,8 @@ getChainTip = ogmiosChainTipToTip <$> mkOgmiosRequest Ogmios.queryChainTipCall ogmiosChainTipToTip :: Ogmios.ChainTipQR -> Chain.Tip ogmiosChainTipToTip = case _ of Ogmios.CtChainOrigin _ -> Chain.TipAtGenesis - Ogmios.CtChainPoint { slot, hash } -> Chain.Tip $ wrap - { slot, blockHeaderHash: wrap $ unwrap hash } + Ogmios.CtChainPoint { slot, id } -> Chain.Tip $ wrap + { slot, blockHeaderHash: wrap $ unwrap id } -------------------------------------------------------------------------------- -- Ogmios Local Tx Submission Protocol @@ -806,21 +806,21 @@ mkSubmitTxListenerSet dispatcher pendingRequests = -- | Builds an Ogmios request action using `QueryM` mkOgmiosRequest :: forall (request :: Type) (response :: Type) - . JsonWsp.JsonWspCall request response + . JsonRpc2.JsonRpc2Call request response -> (OgmiosListeners -> ListenerSet request response) -> request -> QueryM response -mkOgmiosRequest jsonWspCall getLs inp = do +mkOgmiosRequest jsonRpc2Call getLs inp = do listeners' <- asks $ listeners <<< _.ogmiosWs <<< _.runtime websocket <- asks $ underlyingWebSocket <<< _.ogmiosWs <<< _.runtime - mkRequest listeners' websocket jsonWspCall getLs inp + mkRequest listeners' websocket jsonRpc2Call getLs inp -- | Builds an Ogmios request action using `Aff` mkOgmiosRequestAff :: forall (request :: Type) (response :: Type) . OgmiosWebSocket -> Logger - -> JsonWsp.JsonWspCall request response + -> JsonRpc2.JsonRpc2Call request response -> (OgmiosListeners -> ListenerSet request response) -> request -> Aff response @@ -832,13 +832,13 @@ mkRequest :: forall (request :: Type) (response :: Type) (listeners :: Type) . listeners -> JsWebSocket - -> JsonWsp.JsonWspCall request response + -> JsonRpc2.JsonRpc2Call request response -> (listeners -> ListenerSet request response) -> request -> QueryM response -mkRequest listeners' ws jsonWspCall getLs inp = do +mkRequest listeners' ws jsonRpc2Call getLs inp = do logger <- getLogger - liftAff $ mkRequestAff listeners' ws logger jsonWspCall getLs inp + liftAff $ mkRequestAff listeners' ws logger jsonRpc2Call getLs inp getLogger :: QueryM Logger getLogger = do @@ -851,13 +851,13 @@ mkRequestAff . listeners -> JsWebSocket -> Logger - -> JsonWsp.JsonWspCall request response + -> JsonRpc2.JsonRpc2Call request response -> (listeners -> ListenerSet request response) -> request -> Aff response -mkRequestAff listeners' webSocket logger jsonWspCall getLs input = do +mkRequestAff listeners' webSocket logger jsonRpc2Call getLs input = do { body, id } <- - liftEffect $ JsonWsp.buildRequest jsonWspCall input + liftEffect $ JsonRpc2.buildRequest jsonRpc2Call input let respLs :: ListenerSet request response respLs = getLs listeners' diff --git a/src/Internal/QueryM/Dispatcher.purs b/src/Internal/QueryM/Dispatcher.purs index a81e7a755c..9f93c1a883 100644 --- a/src/Internal/QueryM/Dispatcher.purs +++ b/src/Internal/QueryM/Dispatcher.purs @@ -15,7 +15,7 @@ module Ctl.Internal.QueryM.Dispatcher import Prelude import Aeson (Aeson, JsonDecodeError, stringifyAeson) -import Ctl.Internal.QueryM.JsonWsp (parseJsonWspResponseId) +import Ctl.Internal.QueryM.JsonRpc2 (parseJsonRpc2ResponseId) import Ctl.Internal.QueryM.Ogmios (TxHash) import Ctl.Internal.QueryM.UniqueId (ListenerId) import Data.Either (Either(Left, Right)) @@ -64,7 +64,7 @@ newDispatcher = Ref.new Map.empty mkWebsocketDispatch :: Dispatcher -> WebsocketDispatch mkWebsocketDispatch dispatcher aeson = do - case parseJsonWspResponseId aeson of + case parseJsonRpc2ResponseId aeson of Left parseError -> pure $ Left $ JsonError parseError Right reflection -> do diff --git a/src/Internal/QueryM/JsonRpc2.purs b/src/Internal/QueryM/JsonRpc2.purs new file mode 100644 index 0000000000..7cc03725b9 --- /dev/null +++ b/src/Internal/QueryM/JsonRpc2.purs @@ -0,0 +1,124 @@ +-- | Provides basics types and operations for working with JSON RPC protocol +-- | used by Ogmios +module Ctl.Internal.QueryM.JsonRpc2 + ( JsonRpc2Request + , JsonRpc2Response + , JsonRpc2Call + , mkCallType + , buildRequest + , parseJsonRpc2Response + , parseJsonRpc2ResponseId + ) where + +import Prelude + +import Aeson + ( class DecodeAeson + , class EncodeAeson + , Aeson + , JsonDecodeError(TypeMismatch) + , caseAesonObject + , encodeAeson + , getField + , getFieldOptional + ) +import Ctl.Internal.QueryM.UniqueId (ListenerId, uniqueId) +import Data.Either (Either(Left)) +import Data.Maybe (Maybe) +import Effect (Effect) +import Foreign.Object (Object) +import Record as Record + +-- | Structure of all json rpc2.0 websocket requests +-- described in: https://ogmios.dev/getting-started/basics/ +type JsonRpc2Request (a :: Type) = + { jsonrpc :: String + , method :: String + , params :: a + , id :: ListenerId + } + +-- | Convenience helper function for creating `JsonRpc2Request a` objects +mkJsonRpc2Request + :: forall (a :: Type) + . { jsonrpc :: String } + -> { method :: String + , params :: a + } + -> Effect (JsonRpc2Request a) +mkJsonRpc2Request service method = do + id <- uniqueId $ method.method <> "-" + pure + $ Record.merge { id } + $ Record.merge service method + +-- | Structure of all json wsp websocket responses +-- described in: https://ogmios.dev/getting-started/basics/ +type JsonRpc2Response (a :: Type) = + { jsonrpc :: String + -- methodname is not always present if `error` is not empty + , method :: Maybe String + , result :: Maybe a + , error :: Maybe Aeson + , id :: ListenerId + } + +-- | A wrapper for tying arguments and response types to request building. +newtype JsonRpc2Call :: Type -> Type -> Type +newtype JsonRpc2Call (i :: Type) (o :: Type) = JsonRpc2Call + (i -> Effect { body :: Aeson, id :: String }) + +-- | Creates a "jsonwsp call" which ties together request input and response output types +-- | along with a way to create a request object. +mkCallType + :: forall (a :: Type) (i :: Type) (o :: Type) + . EncodeAeson (JsonRpc2Request a) + => { jsonrpc :: String } + -> { method :: String, params :: i -> a } + -> JsonRpc2Call i o +mkCallType service { method, params } = JsonRpc2Call $ \i -> do + req <- mkJsonRpc2Request service { method, params: params i } + pure { body: encodeAeson req, id: req.id } + +-- | Create a JsonRpc2 request body and id +buildRequest + :: forall (i :: Type) (o :: Type) + . JsonRpc2Call i o + -> i + -> Effect { body :: Aeson, id :: String } +buildRequest (JsonRpc2Call c) = c + +-- | Polymorphic response parser +parseJsonRpc2Response + :: forall (a :: Type) + . DecodeAeson a + => Aeson + -> Either JsonDecodeError (JsonRpc2Response a) +parseJsonRpc2Response = aesonObject $ \o -> do + jsonrpc <- getField o "jsonrpc" + method <- getFieldOptional o "method" + result <- getFieldOptional o "result" + error <- getFieldOptional o "error" + id <- getField o "id" + pure + { jsonrpc + , method + , result + , error + , id + } + +-- | Parse just ID from the response +parseJsonRpc2ResponseId + :: Aeson + -> Either JsonDecodeError ListenerId +parseJsonRpc2ResponseId = + aesonObject $ flip getField "id" + +-- | Helper for assuming we get an object +aesonObject + :: forall (a :: Type) + . (Object Aeson -> Either JsonDecodeError a) + -> Aeson + -> Either JsonDecodeError a +aesonObject = caseAesonObject (Left (TypeMismatch "expected object")) diff --git a/src/Internal/QueryM/JsonWsp.purs b/src/Internal/QueryM/JsonWsp.purs deleted file mode 100644 index 5426eb3bbf..0000000000 --- a/src/Internal/QueryM/JsonWsp.purs +++ /dev/null @@ -1,139 +0,0 @@ --- | Provides basics types and operations for working with JSON RPC protocol --- | used by Ogmios -module Ctl.Internal.QueryM.JsonWsp - ( JsonWspRequest - , JsonWspResponse - , JsonWspCall - , mkCallType - , buildRequest - , parseJsonWspResponse - , parseJsonWspResponseId - ) where - -import Prelude - -import Aeson - ( class DecodeAeson - , class EncodeAeson - , Aeson - , JsonDecodeError(TypeMismatch) - , caseAesonObject - , encodeAeson - , getField - , getFieldOptional - ) -import Ctl.Internal.QueryM.UniqueId (ListenerId, uniqueId) -import Data.Either (Either(Left)) -import Data.Maybe (Maybe) -import Effect (Effect) -import Foreign.Object (Object) -import Record as Record - --- | Structure of all json wsp websocket requests --- described in: https://ogmios.dev/getting-started/basics/ -type JsonWspRequest (a :: Type) = - { type :: String - , version :: String - , servicename :: String - , methodname :: String - , args :: a - , mirror :: ListenerId - } - --- | Convenience helper function for creating `JsonWspRequest a` objects -mkJsonWspRequest - :: forall (a :: Type) - . { type :: String - , version :: String - , servicename :: String - } - -> { methodname :: String - , args :: a - } - -> Effect (JsonWspRequest a) -mkJsonWspRequest service method = do - id <- uniqueId $ method.methodname <> "-" - pure - $ Record.merge { mirror: id } - $ - Record.merge service method - --- | Structure of all json wsp websocket responses --- described in: https://ogmios.dev/getting-started/basics/ -type JsonWspResponse (a :: Type) = - { type :: String - , version :: String - , servicename :: String - -- methodname is not always present if `fault` is not empty - , methodname :: Maybe String - , result :: Maybe a - , fault :: Maybe Aeson - , reflection :: ListenerId - } - --- | A wrapper for tying arguments and response types to request building. -newtype JsonWspCall :: Type -> Type -> Type -newtype JsonWspCall (i :: Type) (o :: Type) = JsonWspCall - (i -> Effect { body :: Aeson, id :: String }) - --- | Creates a "jsonwsp call" which ties together request input and response output types --- | along with a way to create a request object. -mkCallType - :: forall (a :: Type) (i :: Type) (o :: Type) - . EncodeAeson (JsonWspRequest a) - => { type :: String - , version :: String - , servicename :: String - } - -> { methodname :: String, args :: i -> a } - -> JsonWspCall i o -mkCallType service { methodname, args } = JsonWspCall $ \i -> do - req <- mkJsonWspRequest service { methodname, args: args i } - pure { body: encodeAeson req, id: req.mirror } - --- | Create a JsonWsp request body and id -buildRequest - :: forall (i :: Type) (o :: Type) - . JsonWspCall i o - -> i - -> Effect { body :: Aeson, id :: String } -buildRequest (JsonWspCall c) = c - --- | Polymorphic response parser -parseJsonWspResponse - :: forall (a :: Type) - . DecodeAeson a - => Aeson - -> Either JsonDecodeError (JsonWspResponse a) -parseJsonWspResponse = aesonObject $ \o -> do - typeField <- getField o "type" - version <- getField o "version" - servicename <- getField o "servicename" - methodname <- getFieldOptional o "methodname" - result <- getFieldOptional o "result" - fault <- getFieldOptional o "fault" - reflection <- getField o "reflection" - pure - { "type": typeField - , version - , servicename - , methodname - , result - , fault - , reflection - } - --- | Parse just ID from the response -parseJsonWspResponseId - :: Aeson - -> Either JsonDecodeError ListenerId -parseJsonWspResponseId = - aesonObject $ flip getField "reflection" - --- | Helper for assuming we get an object -aesonObject - :: forall (a :: Type) - . (Object Aeson -> Either JsonDecodeError a) - -> Aeson - -> Either JsonDecodeError a -aesonObject = caseAesonObject (Left (TypeMismatch "expected object")) diff --git a/src/Internal/QueryM/Ogmios.purs b/src/Internal/QueryM/Ogmios.purs index 89e4f338c5..da61e0a14d 100644 --- a/src/Internal/QueryM/Ogmios.purs +++ b/src/Internal/QueryM/Ogmios.purs @@ -49,11 +49,12 @@ module Ctl.Internal.QueryM.Ogmios , aesonArray , aesonObject , evaluateTxCall - , queryPoolIdsCall + , queryStakePoolsCall , mempoolSnapshotHasTxCall , mempoolSnapshotNextTxCall , mempoolSnpashotSizeAndCapacityCall , mkOgmiosCallType + , mkOgmiosCallTypeNoArgs , queryChainTipCall , queryCurrentEpochCall , queryEraSummariesCall @@ -108,7 +109,8 @@ import Ctl.Internal.Cardano.Types.ScriptRef ( ScriptRef(NativeScriptRef, PlutusScriptRef) ) import Ctl.Internal.Cardano.Types.Transaction - ( Costmdls(Costmdls) + ( CostModel(CostModel) + , Costmdls(Costmdls) , ExUnitPrices , ExUnits , Ipv4(Ipv4) @@ -137,7 +139,7 @@ import Ctl.Internal.Cardano.Types.Value ) import Ctl.Internal.Deserialization.FromBytes (fromBytes) import Ctl.Internal.Helpers (encodeMap, showWithParens) -import Ctl.Internal.QueryM.JsonWsp (JsonWspCall, JsonWspRequest, mkCallType) +import Ctl.Internal.QueryM.JsonRpc2 (JsonRpc2Call, JsonRpc2Request, mkCallType) import Ctl.Internal.Serialization.Address (Slot(Slot)) import Ctl.Internal.Serialization.Hash (Ed25519KeyHash, ed25519KeyHashFromBytes) import Ctl.Internal.Types.BigNum (BigNum) @@ -155,6 +157,7 @@ import Ctl.Internal.Types.EraSummaries , EraSummary(EraSummary) , EraSummaryParameters(EraSummaryParameters) ) +import Ctl.Internal.Types.Int as Csl import Ctl.Internal.Types.Natural (Natural) import Ctl.Internal.Types.Natural (fromString) as Natural import Ctl.Internal.Types.ProtocolParameters @@ -224,56 +227,39 @@ import Untagged.Union (type (|+|), toEither1) -------------------------------------------------------------------------------- -- | Queries Ogmios for the system start Datetime -querySystemStartCall :: JsonWspCall Unit OgmiosSystemStart -querySystemStartCall = mkOgmiosCallType - { methodname: "Query" - , args: const { query: "systemStart" } - } +querySystemStartCall :: JsonRpc2Call Unit OgmiosSystemStart +querySystemStartCall = mkOgmiosCallTypeNoArgs "queryNetwork/startTime" -- | Queries Ogmios for the current epoch -queryCurrentEpochCall :: JsonWspCall Unit CurrentEpoch -queryCurrentEpochCall = mkOgmiosCallType - { methodname: "Query" - , args: const { query: "currentEpoch" } - } +queryCurrentEpochCall :: JsonRpc2Call Unit CurrentEpoch +queryCurrentEpochCall = mkOgmiosCallTypeNoArgs "queryLedgerState/epoch" -- | Queries Ogmios for an array of era summaries, used for Slot arithmetic. -queryEraSummariesCall :: JsonWspCall Unit OgmiosEraSummaries -queryEraSummariesCall = mkOgmiosCallType - { methodname: "Query" - , args: const { query: "eraSummaries" } - } +queryEraSummariesCall :: JsonRpc2Call Unit OgmiosEraSummaries +queryEraSummariesCall = mkOgmiosCallTypeNoArgs "queryLedgerState/eraSummaries" -- | Queries Ogmios for the current protocol parameters -queryProtocolParametersCall :: JsonWspCall Unit OgmiosProtocolParameters -queryProtocolParametersCall = mkOgmiosCallType - { methodname: "Query" - , args: const { query: "currentProtocolParameters" } - } +queryProtocolParametersCall :: JsonRpc2Call Unit OgmiosProtocolParameters +queryProtocolParametersCall = mkOgmiosCallTypeNoArgs + "queryLedgerState/protocolParameters" -- | Queries Ogmios for the chain’s current tip. -queryChainTipCall :: JsonWspCall Unit ChainTipQR -queryChainTipCall = mkOgmiosCallType - { methodname: "Query" - , args: const { query: "chainTip" } - } +queryChainTipCall :: JsonRpc2Call Unit ChainTipQR +queryChainTipCall = mkOgmiosCallTypeNoArgs "queryNetwork/tip" -queryPoolIdsCall :: JsonWspCall Unit PoolIdsR -queryPoolIdsCall = mkOgmiosCallType - { methodname: "Query" - , args: const { query: "poolIds" } - } +queryStakePoolsCall :: JsonRpc2Call Unit PoolIdsR +queryStakePoolsCall = mkOgmiosCallTypeNoArgs "queryLedgerState/stakePools" -queryPoolParameters :: JsonWspCall (Array PoolPubKeyHash) PoolParametersR +queryPoolParameters :: JsonRpc2Call (Array PoolPubKeyHash) PoolParametersR queryPoolParameters = mkOgmiosCallType - { methodname: "Query" - , args: \params -> { query: { poolParameters: params } } + { method: "Query" + , params: \params -> { query: { poolParameters: params } } } -queryDelegationsAndRewards :: JsonWspCall (Array String) DelegationsAndRewardsR +queryDelegationsAndRewards :: JsonRpc2Call (Array String) DelegationsAndRewardsR queryDelegationsAndRewards = mkOgmiosCallType - { methodname: "Query" - , args: \skhs -> + { method: "rewardAccountSummaries" + , params: \skhs -> { query: { delegationsAndRewards: skhs } @@ -289,20 +275,22 @@ type OgmiosAddress = String -- | Sends a serialized signed transaction with its full witness through the -- | Cardano network via Ogmios. -submitTxCall :: JsonWspCall (TxHash /\ CborBytes) SubmitTxR +submitTxCall :: JsonRpc2Call (TxHash /\ CborBytes) SubmitTxR submitTxCall = mkOgmiosCallType - { methodname: "SubmitTx" - , args: { submit: _ } <<< cborBytesToHex <<< snd + { method: "submitTransaction" + , params: \(_ /\ cbor) -> + { transaction: { cbor: cborBytesToHex cbor } + } } -- | Evaluates the execution units of scripts present in a given transaction, -- | without actually submitting the transaction. -evaluateTxCall :: JsonWspCall (CborBytes /\ AdditionalUtxoSet) TxEvaluationR +evaluateTxCall :: JsonRpc2Call (CborBytes /\ AdditionalUtxoSet) TxEvaluationR evaluateTxCall = mkOgmiosCallType - { methodname: "EvaluateTx" - , args: \(cbor /\ utxoqr) -> - { evaluate: cborBytesToHex cbor - , additionalUtxoSet: utxoqr + { method: "evaluateTransaction" + , params: \(cbor /\ utxoqr) -> + { transaction: { cbor: cborBytesToHex cbor } + , additionalUtxo: utxoqr } } @@ -311,33 +299,33 @@ evaluateTxCall = mkOgmiosCallType -- https://ogmios.dev/mini-protocols/local-tx-monitor/ -------------------------------------------------------------------------------- -acquireMempoolSnapshotCall :: JsonWspCall Unit MempoolSnapshotAcquired +acquireMempoolSnapshotCall :: JsonRpc2Call Unit MempoolSnapshotAcquired acquireMempoolSnapshotCall = - mkOgmiosCallTypeNoArgs "AwaitAcquire" + mkOgmiosCallTypeNoArgs "acquireMempool" mempoolSnapshotHasTxCall - :: MempoolSnapshotAcquired -> JsonWspCall TxHash Boolean + :: MempoolSnapshotAcquired -> JsonRpc2Call TxHash Boolean mempoolSnapshotHasTxCall _ = mkOgmiosCallType - { methodname: "HasTx" - , args: { id: _ } + { method: "hasTransacation" + , params: { id: _ } } mempoolSnapshotNextTxCall - :: MempoolSnapshotAcquired -> JsonWspCall Unit (Maybe MempoolTransaction) + :: MempoolSnapshotAcquired -> JsonRpc2Call Unit (Maybe MempoolTransaction) mempoolSnapshotNextTxCall _ = mkOgmiosCallType - { methodname: "NextTx" - , args: const { fields: "all" } + { method: "nextTransaction" + , params: const { fields: "all" } } mempoolSnpashotSizeAndCapacityCall - :: MempoolSnapshotAcquired -> JsonWspCall Unit MempoolSizeAndCapacity + :: MempoolSnapshotAcquired -> JsonRpc2Call Unit MempoolSizeAndCapacity mempoolSnpashotSizeAndCapacityCall _ = - mkOgmiosCallTypeNoArgs "SizeAndCapacity" + mkOgmiosCallTypeNoArgs "sizeOfMempool" releaseMempoolCall - :: MempoolSnapshotAcquired -> JsonWspCall Unit String + :: MempoolSnapshotAcquired -> JsonRpc2Call Unit String releaseMempoolCall _ = - mkOgmiosCallTypeNoArgs "ReleaseMempool" + mkOgmiosCallTypeNoArgs "releaseMempool" -------------------------------------------------------------------------------- -- Local Tx Monitor Query Response & Parsing @@ -397,22 +385,17 @@ instance DecodeAeson MempoolTransaction where -------------------------------------------------------------------------------- mkOgmiosCallTypeNoArgs - :: forall (o :: Type). String -> JsonWspCall Unit o -mkOgmiosCallTypeNoArgs methodname = - mkOgmiosCallType { methodname, args: const {} } + :: forall (o :: Type). String -> JsonRpc2Call Unit o +mkOgmiosCallTypeNoArgs method = + mkOgmiosCallType { method, params: const {} } mkOgmiosCallType :: forall (a :: Type) (i :: Type) (o :: Type) - . EncodeAeson (JsonWspRequest a) - => { methodname :: String, args :: i -> a } - -> JsonWspCall i o + . EncodeAeson (JsonRpc2Request a) + => { method :: String, params :: i -> a } + -> JsonRpc2Call i o mkOgmiosCallType = - ( mkCallType - { "type": "jsonwsp/request" - , version: "1.0" - , servicename: "ogmios" - } - ) + mkCallType { jsonrpc: "2.0" } ---------------- TX SUBMISSION QUERY RESPONSE & PARSING @@ -430,7 +413,7 @@ type TxHash = ByteArray instance DecodeAeson SubmitTxR where decodeAeson = aesonObject $ \o -> - ( getField o "SubmitSuccess" >>= flip getField "txId" >>= hexToByteArray + ( getField o "transaction" >>= flip getField "id" >>= hexToByteArray >>> maybe (Left (TypeMismatch "Expected hexstring")) (pure <<< SubmitTxSuccess) ) <|> (SubmitFail <$> getField o "SubmitFail") @@ -496,7 +479,8 @@ instance DecodeAeson OgmiosEraSummaries where :: Object Aeson -> Either JsonDecodeError EraSummaryParameters decodeEraSummaryParameters o = do epochLength <- getField o "epochLength" - slotLength <- wrap <$> ((*) slotLengthFactor <$> getField o "slotLength") + slotLength <- wrap <$> ((*) slotLengthFactor <$> + (flip getField "seconds" =<< getField o "slotLength")) safeZone <- fromMaybe zero <$> getField o "safeZone" pure $ wrap { epochLength, slotLength, safeZone } @@ -712,21 +696,18 @@ instance Show TxEvaluationResult where show = genericShow instance DecodeAeson TxEvaluationResult where - decodeAeson = aesonObject $ \obj -> do - rdmrPtrExUnitsList :: Array (String /\ Aeson) <- - ForeignObject.toUnfoldable <$> getField obj "EvaluationResult" + decodeAeson = aesonArray $ \array -> do TxEvaluationResult <<< Map.fromFoldable <$> - traverse decodeRdmrPtrExUnitsItem rdmrPtrExUnitsList + traverse decodeRdmrPtrExUnitsItem array where - decodeRdmrPtrExUnitsItem - :: String /\ Aeson - -> Either JsonDecodeError (RedeemerPointer /\ ExecutionUnits) - decodeRdmrPtrExUnitsItem (redeemerPtrRaw /\ exUnitsAeson) = do + decodeRdmrPtrExUnitsItem :: Aeson -> Either JsonDecodeError (RedeemerPointer /\ ExecutionUnits) + decodeRdmrPtrExUnitsItem elem = do + (redeemerPtrRaw /\ exUnitsAeson) :: String /\ Aeson <- decodeAeson elem redeemerPtr <- decodeRedeemerPointer redeemerPtrRaw flip aesonObject exUnitsAeson $ \exUnitsObj -> do memory <- getField exUnitsObj "memory" - steps <- getField exUnitsObj "steps" - pure $ redeemerPtr /\ { memory, steps } + cpu <- getField exUnitsObj "cpu" + pure $ redeemerPtr /\ { memory, steps: cpu } redeemerPtrTypeMismatch :: JsonDecodeError redeemerPtrTypeMismatch = TypeMismatch @@ -903,41 +884,48 @@ rationalToSubcoin (PParamRational rat) = do -- | A type that corresponds to Ogmios response. type ProtocolParametersRaw = { "minFeeCoefficient" :: UInt - , "minFeeConstant" :: UInt - , "maxBlockBodySize" :: UInt - , "maxBlockHeaderSize" :: UInt - , "maxTxSize" :: UInt - , "stakeKeyDeposit" :: BigInt - , "poolDeposit" :: BigInt - , "poolRetirementEpochBound" :: BigInt - , "desiredNumberOfPools" :: UInt - , "poolInfluence" :: PParamRational + , "minFeeConstant" :: + { "lovelace" :: UInt } + , "minUtxoDepositCoefficient" :: BigInt + , "maxBlockBodySize" :: + { "bytes" :: UInt } + , "maxBlockHeaderSize" :: + { "bytes" :: UInt } + , "maxTransactionSize" :: + { "bytes" :: UInt } + , "maxValueSize" :: + { "bytes" :: UInt } + , "stakeCredentialDeposit" :: + { "lovelace" :: BigInt } + , "stakePoolDeposit" :: + { "lovelace" :: BigInt } + , "stakePoolRetirementEpochBound" :: BigInt + , "desiredNumberOfStakePools" :: UInt + , "stakePoolPledgeInfluence" :: PParamRational , "monetaryExpansion" :: PParamRational , "treasuryExpansion" :: PParamRational - , "protocolVersion" :: + , "version" :: { "major" :: UInt , "minor" :: UInt } - , "minPoolCost" :: BigInt - , "coinsPerUtxoByte" :: Maybe BigInt - , "coinsPerUtxoWord" :: Maybe BigInt - , "costModels" :: - { "plutus:v1" :: { | CostModelV1 } - , "plutus:v2" :: Maybe { | CostModelV2 } + , "minStakePoolCost" :: + { "lovelace" :: BigInt } + , "plutusCostModels" :: + { "plutus:v1" :: Array Csl.Int + , "plutus:v2" :: Maybe (Array Csl.Int) } - , "prices" :: + , "scriptExecutionPrices" :: { "memory" :: PParamRational - , "steps" :: PParamRational + , "cpu" :: PParamRational } , "maxExecutionUnitsPerTransaction" :: { "memory" :: BigInt - , "steps" :: BigInt + , "cpu" :: BigInt } , "maxExecutionUnitsPerBlock" :: { "memory" :: BigInt - , "steps" :: BigInt + , "cpu" :: BigInt } - , "maxValueSize" :: UInt , "collateralPercentage" :: UInt , "maxCollateralInputs" :: UInt } @@ -955,54 +943,50 @@ instance DecodeAeson OgmiosProtocolParameters where decodeAeson aeson = do ps :: ProtocolParametersRaw <- decodeAeson aeson prices <- decodePrices ps - coinsPerUtxoUnit <- - maybe - (Left $ AtKey "coinsPerUtxoByte or coinsPerUtxoWord" $ MissingValue) - pure - $ (CoinsPerUtxoByte <<< Coin <$> ps.coinsPerUtxoByte) <|> - (CoinsPerUtxoWord <<< Coin <$> ps.coinsPerUtxoWord) pure $ OgmiosProtocolParameters $ ProtocolParameters - { protocolVersion: ps.protocolVersion.major /\ ps.protocolVersion.minor + { protocolVersion: ps.version.major /\ ps.version.minor -- The following two parameters were removed from Babbage , decentralization: zero , extraPraosEntropy: Nothing - , maxBlockHeaderSize: ps.maxBlockHeaderSize - , maxBlockBodySize: ps.maxBlockBodySize - , maxTxSize: ps.maxTxSize - , txFeeFixed: ps.minFeeConstant + , maxBlockHeaderSize: ps.maxBlockHeaderSize.bytes + , maxBlockBodySize: ps.maxBlockBodySize.bytes + , maxTxSize: ps.maxTransactionSize.bytes + , txFeeFixed: ps.minFeeConstant.lovelace , txFeePerByte: ps.minFeeCoefficient - , stakeAddressDeposit: Coin ps.stakeKeyDeposit - , stakePoolDeposit: Coin ps.poolDeposit - , minPoolCost: Coin ps.minPoolCost - , poolRetireMaxEpoch: Epoch ps.poolRetirementEpochBound - , stakePoolTargetNum: ps.desiredNumberOfPools - , poolPledgeInfluence: unwrap ps.poolInfluence + , stakeAddressDeposit: Coin ps.stakeCredentialDeposit.lovelace + , stakePoolDeposit: Coin ps.stakePoolDeposit.lovelace + , minPoolCost: Coin ps.minStakePoolCost.lovelace + , poolRetireMaxEpoch: Epoch ps.stakePoolRetirementEpochBound + , stakePoolTargetNum: ps.desiredNumberOfStakePools + , poolPledgeInfluence: unwrap ps.stakePoolPledgeInfluence , monetaryExpansion: unwrap ps.monetaryExpansion , treasuryCut: unwrap ps.treasuryExpansion -- Rational - , coinsPerUtxoUnit: coinsPerUtxoUnit + , coinsPerUtxoUnit: CoinsPerUtxoByte (Coin ps.minUtxoDepositCoefficient) , costModels: Costmdls $ Map.fromFoldable $ catMaybes [ pure - (PlutusV1 /\ convertPlutusV1CostModel ps.costModels."plutus:v1") - , (PlutusV2 /\ _) <<< convertPlutusV2CostModel <$> - ps.costModels."plutus:v2" + ( PlutusV1 /\ CostModel + ps.plutusCostModels."plutus:v1" + ) + , (PlutusV2 /\ _) <<< CostModel <$> + ps.plutusCostModels."plutus:v2" ] , prices: prices , maxTxExUnits: decodeExUnits ps.maxExecutionUnitsPerTransaction , maxBlockExUnits: decodeExUnits ps.maxExecutionUnitsPerBlock - , maxValueSize: ps.maxValueSize + , maxValueSize: ps.maxValueSize.bytes , collateralPercent: ps.collateralPercentage , maxCollateralInputs: ps.maxCollateralInputs } where decodeExUnits - :: { memory :: BigInt, steps :: BigInt } -> ExUnits - decodeExUnits { memory, steps } = { mem: memory, steps } + :: { memory :: BigInt, cpu :: BigInt } -> ExUnits + decodeExUnits { memory, cpu } = { mem: memory, steps: cpu } decodePrices :: ProtocolParametersRaw -> Either JsonDecodeError ExUnitPrices decodePrices ps = note (TypeMismatch "ExUnitPrices") do - memPrice <- rationalToSubcoin ps.prices.memory - stepPrice <- rationalToSubcoin ps.prices.steps + memPrice <- rationalToSubcoin ps.scriptExecutionPrices.memory + stepPrice <- rationalToSubcoin ps.scriptExecutionPrices.cpu pure { memPrice, stepPrice } -- ExUnits ---------------- CHAIN TIP QUERY RESPONSE & PARSING @@ -1049,7 +1033,7 @@ instance Show ChainOrigin where type ChainPoint = { slot :: Slot -- See https://github.com/Plutonomicon/cardano-transaction-lib/issues/632 -- for details on why we lose a negligible amount of precision. - , hash :: OgmiosBlockHeaderHash + , id :: OgmiosBlockHeaderHash } ---------------- POOL ID RESPONSE @@ -1131,7 +1115,7 @@ instance EncodeAeson AdditionalUtxoSet where ---------------- UTXO QUERY RESPONSE & PARSING -- the outer result type for Utxo queries, newtyped so that it can have --- appropriate instances to work with `parseJsonWspResponse` +-- appropriate instances to work with `parseJsonRpc2Response` -- | Ogmios response for Utxo Query newtype UtxoQR = UtxoQR UtxoQueryResult @@ -1306,10 +1290,10 @@ newtype Assets = Assets (Map CurrencySymbol (Map TokenName BigInt)) instance DecodeAeson Assets where decodeAeson j = do - wspAssets :: Array (String /\ BigInt) <- + jsonRpc2Assets :: Array (String /\ BigInt) <- ForeignObject.toUnfoldable <$> decodeAeson j Assets <<< Map.fromFoldableWith (Map.unionWith (+)) <$> sequence - (uncurry decodeAsset <$> wspAssets) + (uncurry decodeAsset <$> jsonRpc2Assets) where decodeAsset :: String diff --git a/src/Internal/QueryM/Pools.purs b/src/Internal/QueryM/Pools.purs index 93b2a2d1cf..b8b3989763 100644 --- a/src/Internal/QueryM/Pools.purs +++ b/src/Internal/QueryM/Pools.purs @@ -38,7 +38,7 @@ import Effect.Exception (error) import Record.Builder (build, merge) getPoolIds :: QueryM (Array PoolPubKeyHash) -getPoolIds = mkOgmiosRequest Ogmios.queryPoolIdsCall +getPoolIds = mkOgmiosRequest Ogmios.queryStakePoolsCall _.poolIds unit diff --git a/src/Internal/TxOutput.purs b/src/Internal/TxOutput.purs index 57fd70be35..8bbbb10203 100644 --- a/src/Internal/TxOutput.purs +++ b/src/Internal/TxOutput.purs @@ -58,7 +58,7 @@ transactionInputToTxOutRef , index } --- https://ogmios.dev/ogmios.wsp.json see "datum", potential FIX ME: it says +-- https://ogmios.dev/ogmios.json see "datum", potential FIX ME: it says -- base64 but the example provided looks like a hexadecimal so use -- hexToByteArray for now. https://github.com/Plutonomicon/cardano-transaction-lib/issues/78 -- | Converts an Ogmios transaction output to (internal) `TransactionOutput` diff --git a/test/Ogmios/GenerateFixtures.purs b/test/Ogmios/GenerateFixtures.purs index 3b06818f67..d9da357fc6 100644 --- a/test/Ogmios/GenerateFixtures.purs +++ b/test/Ogmios/GenerateFixtures.purs @@ -26,12 +26,14 @@ import Ctl.Internal.QueryM , mkRequestAff , mkWebsocketDispatch ) -import Ctl.Internal.QueryM.JsonWsp (JsonWspCall) -import Ctl.Internal.QueryM.Ogmios (mkOgmiosCallType) +import Ctl.Internal.QueryM.JsonRpc2 (JsonRpc2Call) +import Ctl.Internal.QueryM.Ogmios (mkOgmiosCallType, mkOgmiosCallTypeNoArgs) import Ctl.Internal.ServerConfig (ServerConfig, mkWsUrl) import Data.Either (Either(Left, Right)) import Data.Log.Level (LogLevel(Trace, Debug)) import Data.Map as Map +import Data.String.Common (replace) +import Data.String.Pattern (Pattern(Pattern), Replacement(Replacement)) import Data.Traversable (for_, traverse_) import Effect (Effect) import Effect.Aff (Aff, Canceler(Canceler), launchAff_, makeAff) @@ -87,18 +89,23 @@ mkWebSocketAff mkWebSocketAff lvl = makeAff <<< map (map (Canceler <<< map liftEffect)) <<< mkWebSocket lvl -data Query = Query (JsonWspCall Unit Aeson) String +data Query = Query (JsonRpc2Call Unit Aeson) String -mkQuery :: forall (query :: Type). EncodeAeson query => query -> String -> Query -mkQuery query shown = Query queryCall shown +mkQuery + :: forall (params :: Type). EncodeAeson params => params -> String -> Query +mkQuery params method = Query queryCall (sanitiseMethod method) where queryCall = mkOgmiosCallType - { methodname: "Query" - , args: const { query } + { method + , params: const { params } } mkQuery' :: String -> Query -mkQuery' query = mkQuery query query +mkQuery' method = Query (mkOgmiosCallTypeNoArgs method) (sanitiseMethod method) + +-- | To avoid creating directories, replace slashes with dashes +sanitiseMethod :: String -> String +sanitiseMethod = replace (Pattern "/") (Replacement "-") main :: Effect Unit main = @@ -121,17 +128,19 @@ main = ] let queries = - [ mkQuery' "currentProtocolParameters" - , mkQuery' "eraSummaries" - , mkQuery' "currentEpoch" - , mkQuery' "systemStart" - , mkQuery' "chainTip" - ] <> flip map addresses \addr -> mkQuery { utxo: [ addr ] } "utxo" - resps <- flip parTraverse queries \(Query qc shown) -> do + [ mkQuery' "queryLedgerState/protocolParameters" + , mkQuery' "queryLedgerState/eraSummaries" + , mkQuery' "queryLedgerState/epoch" + , mkQuery' "queryNetwork/systemStart" + , mkQuery' "queryNetwork/tip" + ] <> flip map addresses \addr -> mkQuery { utxo: [ addr ] } + "queryLedgerStat/utxo" + + resps <- flip parTraverse queries \(Query qc method) -> do resp <- mkRequestAff listeners ws (\_ _ -> pure unit) qc identity unit - pure { resp, query: shown } + pure { resp, method } - for_ resps \{ resp, query } -> do + for_ resps \{ resp, method } -> do let resp' = stringifyAeson resp respMd5 <- liftEffect $ md5HashHex resp' let @@ -139,7 +148,7 @@ main = [ "fixtures" , "test" , "ogmios" - , query <> "-" <> respMd5 <> ".json" + , method <> "-" <> respMd5 <> ".json" ] writeTextFile UTF8 fp resp' log ("Written " <> fp) diff --git a/test/Parser.purs b/test/Parser.purs index 934ea1a1ab..a78f1f0435 100644 --- a/test/Parser.purs +++ b/test/Parser.purs @@ -12,7 +12,7 @@ import Aeson import Control.Monad.Error.Class (throwError) import Control.Monad.Except.Trans (ExceptT, runExceptT) import Control.Monad.Trans.Class (lift) -import Ctl.Internal.QueryM.JsonWsp (JsonWspResponse, parseJsonWspResponse) +import Ctl.Internal.QueryM.JsonRpc2 (JsonRpc2Response, parseJsonRpc2Response) import Ctl.Internal.QueryM.Ogmios (UtxoQR) import Ctl.Internal.Test.TestPlanM (TestPlanM) import Data.Array as Array @@ -33,7 +33,7 @@ import Test.Spec.Assertions (shouldNotSatisfy, shouldSatisfy) suite :: TestPlanM (Aff Unit) Unit suite = do str <- lift $ readTextFile UTF8 - "./fixtures/test/parsing/JsonWsp/UtxoQueryResponse.json" + "./fixtures/test/parsing/JsonRpc2/UtxoQueryResponse.json" let eJson = parseJsonStringToAeson str json <- either @@ -44,7 +44,7 @@ suite = do stringArray = caseAesonArray [] convertJsonArray json :: Array String jsonStrArray = caseAesonArray [] identity json :: Array Aeson schema <- lift $ getSchema - "./fixtures/schemata/JsonWsp/UtxoQueryResponse.medea" + "./fixtures/schemata/JsonRpc2/UtxoQueryResponse.medea" group "Parser tests" $ do group "Schemata parse tests" $ do test "fixture array should not be empty" $ @@ -55,11 +55,11 @@ suite = do isRight group "Type parsing" $ do test "fixtures parse correctly - UtxoQueryResponse" $ - traverseJsonWsps jsonStrArray `shouldSatisfy` isRight + traverseJsonRpc2s jsonStrArray `shouldSatisfy` isRight -traverseJsonWsps - :: Array Aeson -> Either JsonDecodeError (Array (JsonWspResponse UtxoQR)) -traverseJsonWsps arr = traverse parseJsonWspResponse arr +traverseJsonRpc2s + :: Array Aeson -> Either JsonDecodeError (Array (JsonRpc2Response UtxoQR)) +traverseJsonRpc2s arr = traverse parseJsonRpc2Response arr convertJsonArray :: Array Aeson -> Array String convertJsonArray arr = map stringifyAeson arr