Skip to content

Commit

Permalink
Merge pull request #1241 from input-output-hk/ch1bo/fix-protocol-para…
Browse files Browse the repository at this point in the history
…meters-api

Fix /protocol-parameters JSON response
  • Loading branch information
ch1bo authored Jan 10, 2024
2 parents 3c8f217 + 9c13146 commit 4f5f841
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 71 deletions.
4 changes: 2 additions & 2 deletions hydra-cluster/hydra-cluster.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,15 @@ test-suite tests
build-depends:
, aeson
, async
, base >=4.7 && <5
, base >=4.7 && <5
, bytestring
, containers
, directory
, filepath
, hspec
, hydra-cardano-api
, hydra-cluster
, hydra-node
, hydra-node:{hydra-node, testlib}
, hydra-prelude
, hydra-test-utils
, io-classes
Expand Down
11 changes: 9 additions & 2 deletions hydra-cluster/test/Test/Hydra/Cluster/CardanoCliSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Data.Aeson.Lens (key, _String)
import Data.Aeson.Types (parseEither)
import Hydra.API.HTTPServer (DraftCommitTxResponse (DraftCommitTxResponse))
import Hydra.Cardano.Api (Tx)
import Hydra.JSONSchema (validateJSON, withJsonSpecifications)
import Hydra.Ledger.Cardano.Configuration (pparamsFromJson)
import Hydra.Logging (showLogsOnFailure)
import System.Exit (ExitCode (..))
Expand Down Expand Up @@ -46,8 +47,14 @@ spec =
Left e -> failure $ "Failed to decode JSON: " <> e <> "\n" <> show protocolParameters
Right _ -> pure ()

it "query protocol-parameters matches our schema" $ \_tracer ->
pendingWith "TODO"
it "query protocol-parameters matches our schema" $ \tracer ->
withJsonSpecifications $ \tmpDir ->
withCardanoNodeDevnet tracer tmpDir $ \RunningNode{nodeSocket, networkId} -> do
pparamsValue <- cliQueryProtocolParameters nodeSocket (networkId)
validateJSON
(tmpDir </> "api.json")
(key "components" . key "schemas" . key "ProtocolParameters")
pparamsValue
where
cardanoCliSign txFile =
proc
Expand Down
29 changes: 24 additions & 5 deletions hydra-node/hydra-node.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,28 @@ library

ghc-options: -haddock

library testlib
import: project-config
visibility: public
hs-source-dirs: testlib
exposed-modules: Hydra.JSONSchema
other-modules: Paths_hydra_node
build-depends:
, aeson
, base
, containers
, directory
, filepath
, hydra-prelude
, hydra-test-utils
, lens
, lens-aeson
, process
, QuickCheck
, text
, versions
, yaml

executable hydra-node
import: project-config
hs-source-dirs: exe/hydra-node
Expand Down Expand Up @@ -274,7 +296,7 @@ test-suite tests
Hydra.FireForgetSpec
Hydra.HeadLogicSnapshotSpec
Hydra.HeadLogicSpec
Hydra.JSONSchema
Hydra.JSONSchemaSpec
Hydra.Ledger.Cardano.TimeSpec
Hydra.Ledger.CardanoSpec
Hydra.Ledger.SimpleSpec
Expand Down Expand Up @@ -330,7 +352,7 @@ test-suite tests
, hspec-wai
, HUnit
, hydra-cardano-api
, hydra-node
, hydra-node:{hydra-node, testlib}
, hydra-plutus
, hydra-plutus-extras
, hydra-prelude
Expand All @@ -341,7 +363,6 @@ test-suite tests
, lens-aeson
, plutus-ledger-api:{plutus-ledger-api, plutus-ledger-api-testlib} >=1.1.1.0
, plutus-tx
, process
, QuickCheck
, quickcheck-dynamic >=3.3.1 && <3.4
, quickcheck-instances
Expand All @@ -353,9 +374,7 @@ test-suite tests
, time
, typed-protocols-examples >=0.1.0.0
, vector
, versions
, websockets
, yaml

build-tool-depends: hspec-discover:hspec-discover
ghc-options: -threaded -rtsopts
129 changes: 125 additions & 4 deletions hydra-node/json-schemas/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -849,9 +849,7 @@ components:
ProtocolParameters:
title: ProtocolParameters
payload:
type: object
additionalProperties: false
$ref: "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/ProtocolParameters"
$ref: "api.yaml#/components/schemas/ProtocolParameters"

IgnoredHeadInitializing:
title: IgnoredHeadInitializing
Expand Down Expand Up @@ -1056,6 +1054,129 @@ components:
"port": 5001
}

# NOTE: We are not using the cardanonical/cardano.json#ProtocolParameters as
# we need to be compatible with what the cardano-cli provides us
ProtocolParameters:
description: |
Cardano protocol parameters as provided by the cardano-cli and accepted
by the hydra-node on the command line.
type: object
# Allow additional parameters to not be too strict about retired values
# like minUTxOValue
additionalProperties: true
required:
- protocolVersion
- maxBlockHeaderSize
- maxBlockBodySize
- maxTxSize
- txFeeFixed
- txFeePerByte
- stakeAddressDeposit
- stakePoolDeposit
- minPoolCost
- poolRetireMaxEpoch
- stakePoolTargetNum
- poolPledgeInfluence
- monetaryExpansion
- treasuryCut
- costModels # Alonzo onwards
- executionUnitPrices # Alonzo onwards
- maxTxExecutionUnits # Alonzo onwards
- maxBlockExecutionUnits # Alonzo onwards
- maxValueSize # Alonzo onwards
- collateralPercentage # Alonzo onwards
- maxCollateralInputs # Alonzo onwards
- utxoCostPerByte # Babbage onwards
properties:
protocolVersion:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/ProtocolVersion"
maxBlockBodySize:
# XXX: NumberOfBytes in cardanonical
type: integer
minimum: 0
maxBlockHeaderSize:
# XXX: NumberOfBytes in cardanonical
type: integer
minimum: 0
maxTxSize:
# XXX: NumberOfBytes in cardanonical
type: integer
minimum: 0
txFeeFixed:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Lovelace"
txFeePerByte:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/UInt64"
stakeAddressDeposit:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Lovelace"
stakePoolDeposit:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Lovelace"
minPoolCost:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Lovelace"
poolRetireMaxEpoch:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/UInt64"
stakePoolTargetNum:
# XXX: UInt64 in cardanonical, but Natural in cardano-api
type: integer
minimum: 0
poolPledgeInfluence:
# XXX: would be better "$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Ratio"
type: number
monetaryExpansion:
# XXX: would be better "$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Ratio"
type: number
treasuryCut:
# XXX: would be better "$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Ratio"
type: number
costModels:
type: object
# XXX: Different key naming scheme than in cardanonical
propertyNames:
title: Language
type: string
enum:
- "PlutusV1"
- "PlutusV2"
- "PlutusV3"
additionalProperties:
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/CostModel"
executionUnitPrices:
# XXX: Object fields different in cardanonical and using Ratio
priceMemory:
type: number
priceSteps:
type: number
maxTxExecutionUnits:
# XXX: Object fields different in cardanonical
properties:
# XXX: UInt64 in cardanonical, but Rational in cardano-api
memory:
type: number
cpu:
type: number
maxBlockExecutionUnits:
# XXX: Object fields different in cardanonical
properties:
# XXX: UInt64 in cardanonical, but Rational in cardano-api
memory:
type: number
cpu:
type: number
maxValueSize:
# XXX: NumberOfBytes in cardanonical
type: integer
minimum: 0
collateralPercentage:
# XXX: UInt64 in cardanonical, but Natural in cardano-api
type: integer
minimum: 0
maxCollateralInputs:
# XXX: UInt64 in cardanonical, but Natural in cardano-api
type: integer
minimum: 0
utxoCostPerByte:
# XXX: Coefficient in cardanonical is UInt64, but should be lovelace
"$ref": "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/Lovelace"

NodeId:
type: string
description: Hydra Node identifier
Expand Down Expand Up @@ -1901,7 +2022,7 @@ components:
type: object
propertyNames:
pattern: "^[0-9a-f]{64}#[0-9]+$"
items:
items: # REVIEW: does this work? use additionalProperties here?
$ref: "api.yaml#/components/schemas/TxOut"
example:
{
Expand Down
5 changes: 4 additions & 1 deletion hydra-node/src/Hydra/API/HTTPServer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import Hydra.Cardano.Api (
TxOut,
UTxO',
deserialiseFromTextEnvelope,
fromLedgerPParams,
mkScriptWitness,
proxyToAsType,
serialiseToTextEnvelope,
shelleyBasedEra,
pattern KeyWitness,
pattern ScriptWitness,
)
Expand Down Expand Up @@ -166,7 +168,8 @@ httpApp tracer directChain pparams getInitializingHeadId request respond = do
>>= handleDraftCommitUtxo directChain getInitializingHeadId
>>= respond
("GET", ["protocol-parameters"]) ->
respond $ responseLBS status200 [] (Aeson.encode pparams)
respond . responseLBS status200 [] . Aeson.encode $
fromLedgerPParams shelleyBasedEra pparams
("POST", ["cardano-transaction"]) ->
consumeRequestBodyStrict request
>>= handleSubmitUserTx directChain
Expand Down
62 changes: 47 additions & 15 deletions hydra-node/test/Hydra/API/HTTPServerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import Hydra.Prelude hiding (get)
import Test.Hydra.Prelude

import Cardano.Binary (serialize')
import Data.Aeson (Result (Error, Success), Value (String), encode, fromJSON)
import Data.Aeson (Result (Error, Success), Value (String), eitherDecode, encode, fromJSON)
import Data.Aeson.Lens (key, nth)
import Data.ByteString.Base16 qualified as Base16
import Hydra.API.HTTPServer (DraftCommitTxRequest, DraftCommitTxResponse, SubmitTxRequest (..), TransactionSubmitted, httpApp)
import Hydra.API.ServerSpec (dummyChainHandle)
import Hydra.Cardano.Api (serialiseToTextEnvelope, toLedgerTx)
import Hydra.Cardano.Api (fromLedgerPParams, serialiseToTextEnvelope, shelleyBasedEra, toLedgerTx)
import Hydra.Chain.Direct.Fixture (defaultPParams)
import Hydra.Chain.Direct.State ()
import Hydra.JSONSchema (prop_validateJSONSchema)
import Hydra.JSONSchema (SchemaSelector, prop_validateJSONSchema, validateJSON, withJsonSpecifications)
import Hydra.Ledger.Cardano (Tx)
import Hydra.Logging (nullTracer)
import System.FilePath ((</>))
import System.IO.Unsafe (unsafePerformIO)
import Test.Aeson.GenericSpecs (roundtripAndGoldenSpecs)
import Test.Hspec.Wai (MatchBody (..), ResponseMatcher (matchBody), get, shouldRespondWith, with)
import Test.QuickCheck.Property (counterexample, forAll, property, withMaxSuccess)
Expand Down Expand Up @@ -87,17 +88,48 @@ apiServerSpec :: Spec
apiServerSpec = do
with (return webServer) $ do
describe "API should respond correctly" $
it "GET /protocol-parameters works" $
get "/protocol-parameters"
`shouldRespondWith` 200
{ matchBody =
MatchBody
( \_ actualBody ->
if actualBody /= encode defaultPParams
then Just "Request body missmatch"
else Nothing
)
}
describe "GET /protocol-parameters" $ do
it "matches schema" $
withJsonSpecifications $ \schemaDir -> do
get "/protocol-parameters"
`shouldRespondWith` 200
{ matchBody =
matchValidJSON
(schemaDir </> "api.json")
(key "components" . key "messages" . key "ProtocolParameters" . key "payload")
}

it "responds given parameters" $
get "/protocol-parameters"
`shouldRespondWith` 200
{ matchBody = matchJSON $ fromLedgerPParams shelleyBasedEra defaultPParams
}
where
webServer = httpApp nullTracer dummyChainHandle defaultPParams getHeadId
getHeadId = pure Nothing

-- * Helpers

-- | Create a 'ResponseMatcher' or 'MatchBody' from a JSON serializable value
-- (using their 'IsString' instances).
matchJSON :: (IsString s, ToJSON a) => a -> s
matchJSON = fromString . decodeUtf8 . encode

-- | Create a 'MatchBody' that validates the returned JSON response against a
-- schema. NOTE: This raises impure exceptions, so only use it in this test
-- suite.
matchValidJSON :: FilePath -> SchemaSelector -> MatchBody
matchValidJSON schemaFile selector =
MatchBody $ \_headers body ->
case eitherDecode body of
Left err -> Just $ "failed to decode body: " <> err
Right value -> validateJSONPure value
where
-- NOTE: Uses unsafePerformIO to create a pure API although we are actually
-- calling an external program to verify the schema. This is fine, because the
-- call is referentially transparent and any given invocation of schema file,
-- selector and value will always yield the same result and can be shared.
validateJSONPure value =
unsafePerformIO $ do
validateJSON schemaFile selector value
pure Nothing
Loading

0 comments on commit 4f5f841

Please sign in to comment.