-
Notifications
You must be signed in to change notification settings - Fork 32
Feat/hydra #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Feat/hydra #51
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
02ae500
Create kuber-hydra package
reeshavacharya 45e3131
Add hydra docs
reeshavacharya 191816b
Make hydra commads partially functional through kuber
reeshavacharya df917b6
Filter messages based on error tags
reeshavacharya 8a0c743
Generate proper commit response
reeshavacharya aea97a3
Respond with commit transaction on /commit endpoint
reeshavacharya 52d7674
Make on-chain commit transaction functional
reeshavacharya eece16c
Return framework error on commit errors
reeshavacharya 52e44a2
Create framework error handler
reeshavacharya d6b1241
Add functionality to decommit utxo
reeshavacharya ba1584a
Update txBuilder for decommit
reeshavacharya d55239b
Cleanup imports
reeshavacharya 82c23d7
Add close and fanout commands
reeshavacharya bc5064e
Return proper error on hydra comand failure
reeshavacharya afaf06a
Fix un-updated response by using IO monad
reeshavacharya 1f846c1
Create error middleware
reeshavacharya d05e001
Fix protocol-param response
reeshavacharya 212d03a
Use UVerb in api responses
reeshavacharya 7459082
Use same function for close
reeshavacharya 2f3b0ad
Add contest head option
reeshavacharya ade4655
Add wait option on on-chain APIs
reeshavacharya fa09269
Implement filtering UTxOs by txin and address
reeshavacharya 40e3389
Hydra TxBuilder WIP
reeshavacharya d9dc16f
Implement hydra selections in transaction builder
reeshavacharya b92e6ce
Implement hydra inputs in transaction builder
reeshavacharya a81bb6c
Implement change address functionality in hydra tx builder
reeshavacharya 2185a7a
Add submit API
reeshavacharya 41489b8
Merge branch 'master' into feat/hydra
reeshavacharya 2515098
Update CHAP
reeshavacharya dd4ed76
Seperate command and query apis
reeshavacharya 2c846cf
Add hydra head status query api
reeshavacharya 224d352
Add option to show status when head is contested
reeshavacharya b848feb
Create Host data type and pass down to components
reeshavacharya 4008bc9
Rename Host to AppConfig
reeshavacharya 269f284
Make signing key optional in commit and de-commit
reeshavacharya f7e5ffc
Parse address to conwayEra address
reeshavacharya 3644b0d
Use .env in kuber-hydra
reeshavacharya 0f37b58
Add dotenv
reeshavacharya e0c934f
Update Apis with submit boolean query
reeshavacharya 3b7faf9
Seperate ChainQueryApi and CardanoQueryApi
reeshavacharya d7f1555
Hydra client WIP
reeshavacharya 41ae090
Fix spec file for query UTxOs
reeshavacharya 1fba4c9
cleanup
reeshavacharya 4005711
Merge branch 'master' into feat/hydra
reeshavacharya 9b6045d
Remove unused sequence diagrams
reeshavacharya 46da547
Return error on missing UTxO
reeshavacharya 4f9e7c3
Return propoer data structure on hydra protocol parameters
reeshavacharya 9af67b3
Create data type for hydra head state query
reeshavacharya 5763b27
Change command apis to post
reeshavacharya aeff156
Include missing UTxO information in frameworkError
reeshavacharya 2e2cb05
Fix transaction submission within hydra
reeshavacharya 0ea7dad
Pass TxModal instead of creating json object
reeshavacharya 87c67b6
Fix Either monad wrapper in response
reeshavacharya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| HYDRA_IP=172.16.238.10 | ||
| HYDRA_PORT=4001 | ||
| SERVER_PORT=8081 | ||
| CARDANO_NODE_SOCKET_PATH=/media/reeshav/084ef290-597c-4168-b443-49fba520fcb5/cardano-node/preview/node.socket | ||
| NETWORK=2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,4 +3,5 @@ | |
| /.cluster | ||
| # vscode related | ||
| .vscode | ||
| .history | ||
| .history | ||
| .env | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| --- | ||
| sidebar_position: 2 | ||
| --- | ||
|
|
||
| # Scenario Tests | ||
|
|
||
| We have tested the following scenarios in Hydra: | ||
|
|
||
| ## 1. Mint a token and close hydra head | ||
|
|
||
| - A token was minted using a plutusV3 always pass script | ||
| - The snapshot was confirmed for minting token | ||
| - Head was closed | ||
| - Error during fanout | ||
|
|
||
| ```json | ||
| "postTxError": { | ||
| "failureReason": "TxValidationErrorInCardanoMode (ShelleyTxValidationError ShelleyBasedEraConway (ApplyTxError (ConwayUtxowFailure (UtxoFailure (ValueNotConservedUTxO (MaryValue (Coin 325709888) (MultiAsset (fromList [(PolicyID {policyID = ScriptHash \"29c699a1d8dc832504e4ec37a41286176820c9221c5505f7005bae68\"},fromList [(\"4879647261486561645631\",1),(\"e696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab\",1),(\"fce240ccfcb839aa37e5b04206a84530e027b0d3bfb596e7d0685f6a\",1)])]))) (MaryValue (Coin 325709888) (MultiAsset (fromList [(PolicyID {policyID = ScriptHash \"29c699a1d8dc832504e4ec37a41286176820c9221c5505f7005bae68\"},fromList [(\"4879647261486561645631\",1),(\"e696fc821063f9b7311bb350539e67c8fad1bd571605e75b5a353eab\",1),(\"fce240ccfcb839aa37e5b04206a84530e027b0d3bfb596e7d0685f6a\",1)]),(PolicyID {policyID = ScriptHash \"3a888d65f16790950a72daee1f63aa05add6d268434107cfa5b67712\"},fromList [(\"68796472612d6b75626572\",1)])]))))) :| [])))", | ||
| "tag": "FailedToPostTx" | ||
| } | ||
| ``` | ||
|
|
||
| ## 2. Mint a token, burn it and close hydra head | ||
| - A token was minted using a plutusV3 always pass script. | ||
| - The snapshot was confirmed for minting token | ||
| - Token was burnt | ||
| - The snapshot was confirmed for burning token | ||
| - Head was closed | ||
| - Fanout Successful | ||
|
|
||
| ## 3. Pay to script and close hydra head | ||
| - Paid **10 ₳** to script address `addr_test1wqag3rt979nep9g2wtdwu8mr4gz6m4kjdpp5zp705km8wys6t2kla` | ||
| - Snapshot was confirmed for this transaction | ||
| - Head was closed | ||
| - Fanout Successful: [21f398e9a5a7661c326036d5e9577b64f28554da9e26387e780a032fdb77e99a](https://preview.cexplorer.io/tx/21f398e9a5a7661c326036d5e9577b64f28554da9e26387e780a032fdb77e99a) | ||
|
|
||
| ## 4. Pay to 500 addresses and close hydra head | ||
| - Transactions for 500 addresses were done within the hydra head | ||
| - Snapshot was confirmed for each transaction | ||
| - Head was closed | ||
| - Error during fanout | ||
|
|
||
| ```json | ||
| { | ||
| "postTxError": { | ||
| "failureReason": "ValidationFailure (WrapExUnits {unWrapExUnits = ExUnits' {exUnitsMem' = 0, exUnitsSteps' = 0}}) (CekError An error has occurred:\nThe machine terminated part way through evaluation due to overspending the budget.\nThe budget when the machine terminated was:\n({cpu: 6396337807\n| mem: -2582})\nNegative numbers indicate the overspent budget; note that this only indicates the budget that was needed for the next step, not to run the program to completion.) [] ..." | ||
| } | ||
| } | ||
| ``` |
This file was deleted.
Oops, something went wrong.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,254 @@ | ||
| {-# LANGUAGE AllowAmbiguousTypes #-} | ||
| {-# LANGUAGE BlockArguments #-} | ||
| {-# LANGUAGE DataKinds #-} | ||
| {-# LANGUAGE DeriveAnyClass #-} | ||
| {-# LANGUAGE DeriveGeneric #-} | ||
| {-# LANGUAGE DuplicateRecordFields #-} | ||
| {-# LANGUAGE FlexibleContexts #-} | ||
| {-# LANGUAGE FlexibleInstances #-} | ||
| {-# LANGUAGE MonoLocalBinds #-} | ||
| {-# LANGUAGE MultiParamTypeClasses #-} | ||
| {-# LANGUAGE OverloadedRecordDot #-} | ||
| {-# LANGUAGE OverloadedStrings #-} | ||
| {-# LANGUAGE RankNTypes #-} | ||
| {-# LANGUAGE ScopedTypeVariables #-} | ||
| {-# LANGUAGE TypeApplications #-} | ||
| {-# LANGUAGE TypeOperators #-} | ||
| {-# OPTIONS_GHC -Wno-name-shadowing #-} | ||
| {-# OPTIONS_GHC -Wno-orphans #-} | ||
|
|
||
| module Api.Spec where | ||
|
|
||
| import Cardano.Api | ||
| import Cardano.Kuber.Api | ||
| import Cardano.Kuber.Data.Models | ||
| import qualified Data.Aeson as A | ||
| import Data.ByteString (toStrict) | ||
| import qualified Data.ByteString.Char8 as BS | ||
| import qualified Data.ByteString.Lazy as BSL | ||
| import Data.Maybe | ||
| import Data.String | ||
| import qualified Data.Text as T hiding (map) | ||
| import GHC.Generics | ||
| import Network.HTTP.Types (status201, status400) | ||
| import Network.Wai | ||
| import Network.Wai.Middleware.Cors | ||
| import Network.Wai.Middleware.Rewrite | ||
| import Network.Wai.Middleware.Static | ||
| import Servant | ||
| import Servant.Exception | ||
| import Websocket.Aeson | ||
| import Websocket.Commands | ||
| import Websocket.Middleware | ||
| import Websocket.TxBuilder (hydraProtocolParams, queryUTxO, toValidHydraTxBuilder) | ||
| import Websocket.Utils | ||
|
|
||
| -- Define CORS policy | ||
| corsMiddlewarePolicy :: CorsResourcePolicy | ||
| corsMiddlewarePolicy = | ||
| CorsResourcePolicy | ||
| { corsOrigins = Nothing, | ||
| corsMethods = [BS.pack "GET", BS.pack "POST", BS.pack "OPTIONS"], | ||
| corsRequestHeaders = [fromString "content-type", fromString "api-key"], | ||
| corsExposedHeaders = Nothing, | ||
| corsMaxAge = Just 3600, | ||
| corsVaryOrigin = True, | ||
| corsRequireOrigin = False, | ||
| corsIgnoreFailures = True | ||
| } | ||
|
|
||
| newtype ResponseMessage = ResponseMessage | ||
| { result :: String | ||
| } | ||
| deriving (Show, Generic) | ||
|
|
||
| instance ToJSON ResponseMessage | ||
|
|
||
| data CommitUTxOs = CommitUTxOs | ||
| { utxos :: [TxIn], | ||
| signKey :: Maybe A.Value | ||
| } | ||
| deriving (Show, Generic, FromJSON, ToJSON) | ||
|
|
||
| instance ToServantErr FrameworkError where | ||
| status (FrameworkError _ _) = status400 | ||
| status (FrameworkErrors _) = status400 | ||
|
|
||
| instance MimeRender PlainText FrameworkError where | ||
| mimeRender ct = mimeRender ct . show | ||
|
|
||
| type GetResp = UVerb 'GET '[JSON] UVerbResponseTypes | ||
|
|
||
| type PostResp = UVerb 'POST '[JSON] UVerbResponseTypes | ||
|
|
||
| type WithWait sub = QueryParam "wait" Bool :> sub | ||
|
|
||
| type WithSubmit sub = QueryParam "submit" Bool :> sub | ||
|
|
||
| type API = | ||
| "hydra" :> HydraCommandAPI | ||
| :<|> "hydra" :> "query" :> HydraQueryAPI | ||
|
|
||
| type HydraCommandAPI = | ||
| "init" :> WithWait PostResp | ||
| :<|> "abort" :> WithWait PostResp | ||
| :<|> "commit" :> WithSubmit (ReqBody '[JSON] CommitUTxOs :> PostResp) | ||
| :<|> "decommit" :> WithSubmit (WithWait (ReqBody '[JSON] CommitUTxOs :> PostResp)) | ||
| :<|> "close" :> WithWait PostResp | ||
| :<|> "contest" :> WithWait PostResp | ||
| :<|> "fanout" :> WithWait PostResp | ||
| :<|> "tx" :> WithSubmit (ReqBody '[JSON] TxBuilder :> Post '[JSON] TxModal) | ||
| :<|> "submit" :> ReqBody '[JSON] TxModal :> PostResp | ||
|
|
||
| type HydraQueryAPI = | ||
| "utxo" :> QueryParams "address" T.Text :> QueryParams "txin" T.Text :> GetResp | ||
| :<|> "protocol-parameters" :> GetResp | ||
| :<|> "state" :> GetResp | ||
|
|
||
| frameworkErrorHandler valueOrFe = case valueOrFe of | ||
| Left fe -> throwError $ err500 {errBody = BSL.fromStrict $ prettyPrintJSON fe} | ||
| Right val -> respond $ WithStatus @200 val | ||
|
|
||
| toServerError :: FrameworkError -> Handler a | ||
| toServerError err = | ||
| throwError $ | ||
| err500 {errBody = BSL.fromStrict $ prettyPrintJSON err} | ||
|
|
||
| hydraErrorHandler (msg, status) = do | ||
| let jsonResponseOrError = textToJSON msg | ||
| case jsonResponseOrError of | ||
| Left fe -> toServerError fe | ||
| Right jsonResponse -> | ||
| case status of | ||
| 200 -> respond $ WithStatus @200 jsonResponse | ||
| 201 -> respond $ WithStatus @201 jsonResponse | ||
| _ -> | ||
| throwError $ | ||
| (errorMiddleware status) | ||
| { errHTTPCode = status, | ||
| errReasonPhrase = "", | ||
| errBody = A.encode jsonResponse, | ||
| errHeaders = [("Content-Type", "application/json")] | ||
| } | ||
|
|
||
| -- Define Handlers | ||
| server appConfig = | ||
| commandServer appConfig | ||
| :<|> queryServer appConfig | ||
| where | ||
| -- Commands: POSTs and state-changing GETs | ||
| commandServer :: AppConfig -> Server HydraCommandAPI | ||
| commandServer appConfig = | ||
| initHandler appConfig | ||
| :<|> abortHandler appConfig | ||
| :<|> commitHandler appConfig | ||
| :<|> decommitHandler appConfig | ||
| :<|> closeHandler appConfig | ||
| :<|> contestHandler appConfig | ||
| :<|> fanoutHandler appConfig | ||
| :<|> txHandler appConfig | ||
| :<|> submitHandler appConfig | ||
| -- Queries: GET-only, read-only endpoints | ||
| queryServer :: AppConfig -> Server HydraQueryAPI | ||
| queryServer appConfig = | ||
| queryUtxoHandler appConfig | ||
| :<|> queryProtocolParameterHandler appConfig | ||
| :<|> queryStateHandler appConfig | ||
|
|
||
| initHandler :: AppConfig -> Maybe Bool -> Handler (Union UVerbResponseTypes) | ||
| initHandler appConfig wait = do | ||
| initResponse <- liftIO $ initialize appConfig (fromMaybe False wait) | ||
| hydraErrorHandler initResponse | ||
|
|
||
| abortHandler :: AppConfig -> Maybe Bool -> Handler (Union UVerbResponseTypes) | ||
| abortHandler appConfig wait = do | ||
| abortResponse <- liftIO $ abort appConfig (fromMaybe False wait) | ||
| hydraErrorHandler abortResponse | ||
|
|
||
| queryUtxoHandler :: AppConfig -> [T.Text] -> [T.Text] -> Handler (Union UVerbResponseTypes) | ||
| queryUtxoHandler appConfig address txin = do | ||
| parsedTxIns <- liftIO $ listOfTextToTxIn txin | ||
| parsedAddresses <- liftIO $ listOfTextToAddressInEra address | ||
| eitherErrorOrUTxOs <- case parsedTxIns of | ||
| Left fe -> pure $ Left fe | ||
| Right txins -> case parsedAddresses of | ||
| Left fe -> pure $ Left fe | ||
| Right address' -> do | ||
| queryUtxoResponse <- liftIO $ queryUTxO appConfig address' txins | ||
| case queryUtxoResponse of | ||
| Left fe -> pure $ Left fe | ||
| Right utxos -> case bytestringToJSON $ BSL.fromStrict $ serialiseToJSON utxos of | ||
| Left fe' -> pure $ Left fe' | ||
| Right json -> pure $ Right json | ||
| frameworkErrorHandler eitherErrorOrUTxOs | ||
|
|
||
| commitHandler :: AppConfig -> Maybe Bool -> CommitUTxOs -> Handler (Union UVerbResponseTypes) | ||
| commitHandler appConfig submit commits = do | ||
| commitResult <- liftIO $ commitUTxO appConfig commits.utxos (signKey commits) (fromMaybe False submit) | ||
| commitResultToJSON <- case commitResult of | ||
| Left fe -> pure $ Left fe | ||
| Right res -> | ||
| case bytestringToJSON $ A.encode res of | ||
| Left fe -> pure $ Left fe | ||
| Right val -> pure $ Right val | ||
| frameworkErrorHandler commitResultToJSON | ||
|
|
||
| decommitHandler :: AppConfig -> Maybe Bool -> Maybe Bool -> CommitUTxOs -> Handler (Union UVerbResponseTypes) | ||
| decommitHandler appConfig submit wait decommits = do | ||
| decommitResult <- liftIO $ decommitUTxO appConfig decommits.utxos (signKey decommits) (fromMaybe False wait) (fromMaybe False submit) | ||
| frameworkErrorHandler decommitResult | ||
|
|
||
| closeHandler :: AppConfig -> Maybe Bool -> Handler (Union UVerbResponseTypes) | ||
| closeHandler appConfig wait = do | ||
| closeResponse <- liftIO $ close appConfig (fromMaybe False wait) | ||
| hydraErrorHandler closeResponse | ||
|
|
||
| contestHandler :: AppConfig -> Maybe Bool -> Handler (Union UVerbResponseTypes) | ||
| contestHandler appConfig wait = do | ||
| closeResponse <- liftIO $ contest appConfig (fromMaybe False wait) | ||
| hydraErrorHandler closeResponse | ||
|
|
||
| fanoutHandler :: AppConfig -> Maybe Bool -> Handler (Union UVerbResponseTypes) | ||
| fanoutHandler appConfig wait = do | ||
| fanoutResponse <- liftIO $ fanout appConfig (fromMaybe False wait) | ||
| hydraErrorHandler fanoutResponse | ||
|
|
||
| queryProtocolParameterHandler :: AppConfig -> Handler (Union UVerbResponseTypes) | ||
| queryProtocolParameterHandler appConfig = do | ||
| pParamResponse <- liftIO (hydraProtocolParams appConfig :: IO (Either FrameworkError HydraProtocolParameters)) | ||
| pParamsToJSON <- case pParamResponse of | ||
| Left fe -> pure $ Left fe | ||
| Right pparams -> case bytestringToJSON $ A.encode pparams of | ||
| Left fe -> pure $ Left fe | ||
| Right val -> pure $ Right val | ||
| frameworkErrorHandler pParamsToJSON | ||
|
|
||
| queryStateHandler :: AppConfig -> Handler (Union UVerbResponseTypes) | ||
| queryStateHandler appConfig = do | ||
| stateResponse <- liftIO $ getHydraState appConfig | ||
| stateResponseJSON <- case stateResponse of | ||
| Left fe -> pure $ Left fe | ||
| Right stateInfo -> case bytestringToJSON $ A.encode stateInfo of | ||
| Left fe -> pure $ Left fe | ||
| Right val -> pure $ Right val | ||
| frameworkErrorHandler stateResponseJSON | ||
|
|
||
| txHandler :: AppConfig -> Maybe Bool -> TxBuilder_ ConwayEra -> Handler TxModal | ||
| txHandler appConfig submit txb = do | ||
| hydraTxModal <- liftIO $ toValidHydraTxBuilder appConfig txb (fromMaybe False submit) | ||
| case hydraTxModal of | ||
| Left fe -> toServerError fe | ||
| Right txm -> pure txm | ||
|
|
||
| submitHandler :: AppConfig -> TxModal -> Handler (Union UVerbResponseTypes) | ||
| submitHandler appConfig txm = do | ||
| submitResponse <- liftIO $ submit appConfig txm | ||
| hydraErrorHandler submitResponse | ||
|
|
||
| -- Create API Proxy | ||
| deployAPI :: Proxy API | ||
| deployAPI = Proxy | ||
|
|
||
| -- Define Hydra application | ||
| hydraApp :: AppConfig -> Application | ||
| hydraApp appConfig = rewriteRoot (T.pack "index.html") $ static $ cors (const $ Just corsMiddlewarePolicy) $ serve deployAPI (server appConfig) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,26 @@ | ||
| {-# LANGUAGE OverloadedStrings #-} | ||
| {-# OPTIONS_GHC -Wno-name-shadowing #-} | ||
|
|
||
| module Main where | ||
| import Websocket.Connect | ||
|
|
||
| import Api.Spec (hydraApp) | ||
| import Configuration.Dotenv | ||
| import Network.Wai.Handler.Warp | ||
| import Network.Wai.Handler.WebSockets | ||
| import qualified Network.WebSockets as WS | ||
| import System.Environment | ||
| import Websocket.Aeson | ||
| import Websocket.Middleware | ||
| import Websocket.SocketConnection | ||
|
|
||
| main :: IO () | ||
| main = | ||
| putStrLn "hello hydra" | ||
| -- webSocketProxy "172.16.238.10" 4001 | ||
| main = do | ||
| loadFile defaultConfig | ||
| hydraIp <- getEnv "HYDRA_IP" | ||
| hydraPort <- getEnv "HYDRA_PORT" | ||
| serverPort <- getEnv "SERVER_PORT" | ||
| putStrLn $ "Starting HTTP and WebSocket server on port " ++ show serverPort | ||
| putStrLn $ "Hydra node running on " <> hydraIp <> ":" <> hydraPort | ||
| let host = AppConfig hydraIp (read hydraPort) "0.0.0.0" (read serverPort) | ||
| noCacheApp = noCacheMiddleware (hydraApp host) | ||
| run (read serverPort) $ websocketsOr WS.defaultConnectionOptions (proxyServer host) noCacheApp | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a log showing the hydra ip / port
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed