Skip to content

Commit

Permalink
Merge pull request #989 from input-output-hk/endpoint-to-return-proto…
Browse files Browse the repository at this point in the history
…col-params

Ability to read protocol parameters via REST
  • Loading branch information
pgrange authored Jul 26, 2023
2 parents 651c49b + 924e0eb commit 1fa4185
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 45 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ changes.

- `Greetings` message now contains also the hydra-node version.

- New HTTP endpoint (`GET /protocol-parameters`) which provides the current protocol parameters
used by the hydra-node when validating transactions.

- *Fixed bugs* in `hydra-node`:
+ Multisignature verification would silently ignore certain keys in case the
list of verification keys is not of same length as the list of signatures.
Expand Down
15 changes: 6 additions & 9 deletions hydra-node/exe/hydra-node/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ main = do
withDirectChain (contramap DirectChain tracer) chainConfig ctx wallet (getChainState hs) (putEvent . OnChainEvent) $ \chain -> do
let RunOptions{host, port, peers, nodeId} = opts
putNetworkEvent (Authenticated msg otherParty) = putEvent $ NetworkEvent defaultTTL otherParty msg
RunOptions{apiHost, apiPort} = opts
RunOptions{apiHost, apiPort, ledgerConfig} = opts
protocolParams <- readJsonFileThrow protocolParametersFromJson (cardanoLedgerProtocolParametersFile ledgerConfig)
apiPersistence <- createPersistenceIncremental $ persistenceDir <> "/server-output"
withAPIServer apiHost apiPort party apiPersistence (contramap APIServer tracer) chain (putEvent . ClientEvent) $ \server -> do
withAPIServer apiHost apiPort party apiPersistence (contramap APIServer tracer) chain protocolParams (putEvent . ClientEvent) $ \server -> do
withNetwork tracer server signingKey otherParties host port peers nodeId putNetworkEvent $ \hn -> do
let RunOptions{ledgerConfig} = opts
withCardanoLedger ledgerConfig chainConfig $ \ledger ->
withCardanoLedger chainConfig protocolParams $ \ledger ->
runHydraNode (contramap Node tracer) $
HydraNode{eq, hn = contramap (`Authenticated` party) hn, nodeState, oc = chain, server, ledger, env, persistence}

Expand All @@ -127,13 +127,10 @@ main = do
Disconnected nodeid -> sendOutput $ PeerDisconnected nodeid
in withAuthentication (contramap Authentication tracer) signingKey parties $ withHeartbeat nodeId connectionMessages $ withOuroborosNetwork (contramap Network tracer) localhost peers

withCardanoLedger ledgerConfig chainConfig action = do
withCardanoLedger chainConfig protocolParams action = do
let DirectChainConfig{networkId, nodeSocket} = chainConfig
globals <- newGlobals =<< queryGenesisParameters networkId nodeSocket QueryTip
ledgerEnv <-
newLedgerEnv
<$> readJsonFileThrow protocolParametersFromJson (cardanoLedgerProtocolParametersFile ledgerConfig)

let ledgerEnv = newLedgerEnv protocolParams
action (Ledger.cardanoLedger globals ledgerEnv)

-- check if hydra-node parameters are matching with the hydra-node state.
Expand Down
1 change: 1 addition & 0 deletions hydra-node/hydra-node.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ test-suite tests
, hspec
, hspec-core
, hspec-golden-aeson
, hspec-wai
, HUnit
, hydra-cardano-api
, hydra-node
Expand Down
21 changes: 21 additions & 0 deletions hydra-node/json-schemas/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ channels:
type: request
method: POST
bindingVersion: '0.1.0'
/protocol-parameters:
description: Endpoint to obtain protocol parameters used by the internal ledger of a hydra-node.
servers:
- localhost-http
subscribe:
summary: Get cardano protocol parameters.
operationId: getProtocolParameters
message:
$ref: "#/components/messages/ProtocolParameters"
bindings:
http:
type: response
method: GET
bindingVersion: '0.1.0'
components:
messages:
########
Expand Down Expand Up @@ -787,6 +801,13 @@ components:
payload:
$ref: "api.yaml#/components/schemas/TextEnvelopeTransaction"

ProtocolParameters:
title: ProtocolParameters
payload:
type: object
additionalProperties: false
$ref: "https://raw.githubusercontent.com/CardanoSolutions/cardanonical/main/cardano.json#/definitions/ProtocolParameters<Babbage>"

########
#
# Schemas
Expand Down
49 changes: 30 additions & 19 deletions hydra-node/src/Hydra/API/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import Hydra.API.ServerOutput (
projectSnapshotUtxo,
snapshotUtxo,
)
import Hydra.Cardano.Api (ProtocolParameters)
import Hydra.Chain (
Chain (..),
IsChainState,
Expand All @@ -56,6 +57,7 @@ import Hydra.Party (Party)
import Hydra.Persistence (PersistenceIncremental (..))
import Network.HTTP.Types (Method, status200, status400, status500)
import Network.Wai (
Application,
Request (pathInfo),
Response,
ResponseReceived,
Expand Down Expand Up @@ -139,8 +141,9 @@ withAPIServer ::
PersistenceIncremental (TimedServerOutput tx) IO ->
Tracer IO APIServerLog ->
Chain tx IO ->
ProtocolParameters ->
ServerComponent tx IO ()
withAPIServer host port party PersistenceIncremental{loadAll, append} tracer chain callback action = do
withAPIServer host port party PersistenceIncremental{loadAll, append} tracer chain pparams callback action = do
responseChannel <- newBroadcastTChanIO
timedOutputEvents <- loadAll

Expand All @@ -153,7 +156,7 @@ withAPIServer host port party PersistenceIncremental{loadAll, append} tracer cha
history <- newTVarIO (reverse timedOutputEvents)
(notifyServerRunning, waitForServerRunning) <- setupServerNotification
race_
(runAPIServer host port party tracer history chain callback headStatusP snapshotUtxoP responseChannel notifyServerRunning)
(runAPIServer host port party tracer history chain callback headStatusP snapshotUtxoP responseChannel notifyServerRunning pparams)
( do
waitForServerRunning
action $
Expand Down Expand Up @@ -211,13 +214,14 @@ runAPIServer ::
TChan (TimedServerOutput tx) ->
-- | Called when the server is listening before entering the main loop.
NotifyServerRunning ->
ProtocolParameters ->
IO ()
runAPIServer host port party tracer history chain callback headStatusP snapshotUtxoP responseChannel notifyServerRunning = do
runAPIServer host port party tracer history chain callback headStatusP snapshotUtxoP responseChannel notifyServerRunning protocolparams = do
traceWith tracer (APIServerStarted port)
-- catch and rethrow with more context
handle onIOException $
runSettings serverSettings $
websocketsOr defaultConnectionOptions wsApp (httpApp chain)
websocketsOr defaultConnectionOptions wsApp (httpApp tracer chain protocolparams)
where
serverSettings =
defaultSettings
Expand Down Expand Up @@ -248,21 +252,6 @@ runAPIServer host port party tracer history chain callback headStatusP snapshotU
withPingThread con 30 (pure ()) $
race_ (receiveInputs con) (sendOutputs chan con outConfig)

-- Hydra HTTP server
httpApp directChain req respond =
case (requestMethod req, pathInfo req) of
("POST", ["commit"]) -> do
body <- consumeRequestBodyStrict req
handleDraftCommitUtxo directChain tracer body (requestMethod req) (pathInfo req) respond
_ -> do
traceWith tracer $
APIRestInputReceived
{ method = decodeUtf8 $ requestMethod req
, paths = pathInfo req
, requestInputBody = Nothing
}
respond $ responseLBS status400 [] "Resource not found"

-- NOTE: We will add a 'Greetings' message on each API server start. This is
-- important to make sure the latest configured 'party' is reaching the
-- client.
Expand Down Expand Up @@ -360,6 +349,28 @@ data RunServerException = RunServerException

instance Exception RunServerException

-- | Hydra HTTP server
httpApp ::
Tracer IO APIServerLog ->
Chain tx IO ->
ProtocolParameters ->
Application
httpApp tracer directChain pparams req respond =
case (requestMethod req, pathInfo req) of
("POST", ["commit"]) -> do
body <- consumeRequestBodyStrict req
handleDraftCommitUtxo directChain tracer body (requestMethod req) (pathInfo req) respond
("GET", ["protocol-parameters"]) ->
respond $ responseLBS status200 [] (Aeson.encode pparams)
_ -> do
traceWith tracer $
APIRestInputReceived
{ method = decodeUtf8 $ requestMethod req
, paths = pathInfo req
, requestInputBody = Nothing
}
respond $ responseLBS status400 [] "Resource not found"

-- Handle user requests to obtain a draft commit tx
handleDraftCommitUtxo ::
Chain tx IO ->
Expand Down
59 changes: 43 additions & 16 deletions hydra-node/test/Hydra/API/RestServerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,56 @@

module Hydra.API.RestServerSpec where

import Hydra.Prelude
import Hydra.Prelude hiding (get)
import Test.Hydra.Prelude

import Data.Aeson (encode)
import Data.Aeson.Lens (key)
import Hydra.API.RestServer (DraftCommitTxRequest, DraftCommitTxResponse)
import Hydra.API.Server (httpApp)
import Hydra.API.ServerSpec (dummyChainHandle)
import Hydra.Chain.Direct.Fixture (defaultPParams)
import Hydra.Chain.Direct.State ()
import Hydra.JSONSchema (prop_validateJSONSchema)
import Hydra.Logging (nullTracer)
import Test.Aeson.GenericSpecs (roundtripAndGoldenSpecs)
import Test.Hspec.Wai (MatchBody (..), ResponseMatcher (matchBody), get, shouldRespondWith, with)
import Test.QuickCheck.Property (property, withMaxSuccess)

spec :: Spec
spec = parallel $ do
roundtripAndGoldenSpecs (Proxy @(ReasonablySized DraftCommitTxResponse))
roundtripAndGoldenSpecs (Proxy @(ReasonablySized DraftCommitTxRequest))

prop "Validate /commit publish api schema" $
property $
withMaxSuccess 1 $
prop_validateJSONSchema @DraftCommitTxRequest "api.json" $
key "components" . key "messages" . key "DraftCommitTxRequest" . key "payload"

prop "Validate /commit subscribe api schema" $
property $
withMaxSuccess 1 $
prop_validateJSONSchema @DraftCommitTxResponse "api.json" $
key "components" . key "messages" . key "DraftCommitTxResponse" . key "payload"
spec = do
parallel $ do
roundtripAndGoldenSpecs (Proxy @(ReasonablySized DraftCommitTxResponse))
roundtripAndGoldenSpecs (Proxy @(ReasonablySized DraftCommitTxRequest))

prop "Validate /commit publish api schema" $
property $
withMaxSuccess 1 $
prop_validateJSONSchema @DraftCommitTxRequest "api.json" $
key "components" . key "messages" . key "DraftCommitTxRequest" . key "payload"

prop "Validate /commit subscribe api schema" $
property $
withMaxSuccess 1 $
prop_validateJSONSchema @DraftCommitTxResponse "api.json" $
key "components" . key "messages" . key "DraftCommitTxResponse" . key "payload"

apiServerSpec

-- TODO: we should add more tests for other routes here (eg. /commit)
apiServerSpec :: Spec
apiServerSpec = do
let webServer = httpApp nullTracer dummyChainHandle defaultPParams
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
)
}
3 changes: 2 additions & 1 deletion hydra-node/test/Hydra/API/ServerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import Hydra.Chain (
draftCommitTx,
postTx,
)
import Hydra.Chain.Direct.Fixture (defaultPParams)
import Hydra.Ledger (txId)
import Hydra.Ledger.Simple (SimpleTx)
import Hydra.Logging (Tracer, showLogsOnFailure)
Expand Down Expand Up @@ -433,7 +434,7 @@ withTestAPIServer ::
(Server SimpleTx IO -> IO ()) ->
IO ()
withTestAPIServer port actor persistence tracer =
withAPIServer @SimpleTx "127.0.0.1" port actor persistence tracer dummyChainHandle noop
withAPIServer @SimpleTx "127.0.0.1" port actor persistence tracer dummyChainHandle defaultPParams noop

withClient :: PortNumber -> String -> (Connection -> IO ()) -> IO ()
withClient port path action = do
Expand Down

0 comments on commit 1fa4185

Please sign in to comment.