Skip to content
Merged
Show file tree
Hide file tree
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 Jan 6, 2025
45e3131
Add hydra docs
reeshavacharya Jan 28, 2025
191816b
Make hydra commads partially functional through kuber
reeshavacharya Apr 18, 2025
df917b6
Filter messages based on error tags
reeshavacharya Apr 21, 2025
8a0c743
Generate proper commit response
reeshavacharya Apr 21, 2025
aea97a3
Respond with commit transaction on /commit endpoint
reeshavacharya Apr 21, 2025
52d7674
Make on-chain commit transaction functional
reeshavacharya Apr 23, 2025
eece16c
Return framework error on commit errors
reeshavacharya Apr 23, 2025
52e44a2
Create framework error handler
reeshavacharya Apr 24, 2025
d6b1241
Add functionality to decommit utxo
reeshavacharya Apr 25, 2025
ba1584a
Update txBuilder for decommit
reeshavacharya Apr 28, 2025
d55239b
Cleanup imports
reeshavacharya Apr 28, 2025
82c23d7
Add close and fanout commands
reeshavacharya Apr 28, 2025
bc5064e
Return proper error on hydra comand failure
reeshavacharya Apr 28, 2025
afaf06a
Fix un-updated response by using IO monad
reeshavacharya Apr 29, 2025
1f846c1
Create error middleware
reeshavacharya Apr 30, 2025
d05e001
Fix protocol-param response
reeshavacharya Apr 30, 2025
212d03a
Use UVerb in api responses
reeshavacharya May 1, 2025
7459082
Use same function for close
reeshavacharya May 1, 2025
2f3b0ad
Add contest head option
reeshavacharya May 1, 2025
ade4655
Add wait option on on-chain APIs
reeshavacharya May 5, 2025
fa09269
Implement filtering UTxOs by txin and address
reeshavacharya May 5, 2025
40e3389
Hydra TxBuilder WIP
reeshavacharya May 6, 2025
d9dc16f
Implement hydra selections in transaction builder
reeshavacharya May 7, 2025
b92e6ce
Implement hydra inputs in transaction builder
reeshavacharya May 7, 2025
a81bb6c
Implement change address functionality in hydra tx builder
reeshavacharya May 7, 2025
2185a7a
Add submit API
reeshavacharya May 7, 2025
41489b8
Merge branch 'master' into feat/hydra
reeshavacharya May 7, 2025
2515098
Update CHAP
reeshavacharya May 7, 2025
dd4ed76
Seperate command and query apis
reeshavacharya May 8, 2025
2c846cf
Add hydra head status query api
reeshavacharya May 8, 2025
224d352
Add option to show status when head is contested
reeshavacharya May 8, 2025
b848feb
Create Host data type and pass down to components
reeshavacharya May 9, 2025
4008bc9
Rename Host to AppConfig
reeshavacharya May 9, 2025
269f284
Make signing key optional in commit and de-commit
reeshavacharya May 15, 2025
f7e5ffc
Parse address to conwayEra address
reeshavacharya May 15, 2025
3644b0d
Use .env in kuber-hydra
reeshavacharya May 15, 2025
0f37b58
Add dotenv
reeshavacharya May 19, 2025
e0c934f
Update Apis with submit boolean query
reeshavacharya May 19, 2025
3b7faf9
Seperate ChainQueryApi and CardanoQueryApi
reeshavacharya May 21, 2025
d7f1555
Hydra client WIP
reeshavacharya May 22, 2025
41ae090
Fix spec file for query UTxOs
reeshavacharya May 22, 2025
1fba4c9
cleanup
reeshavacharya May 22, 2025
4005711
Merge branch 'master' into feat/hydra
reeshavacharya May 22, 2025
9b6045d
Remove unused sequence diagrams
reeshavacharya May 22, 2025
46da547
Return error on missing UTxO
reeshavacharya May 22, 2025
4f9e7c3
Return propoer data structure on hydra protocol parameters
reeshavacharya May 22, 2025
9af67b3
Create data type for hydra head state query
reeshavacharya May 22, 2025
5763b27
Change command apis to post
reeshavacharya May 22, 2025
aeff156
Include missing UTxO information in frameworkError
reeshavacharya May 22, 2025
2e2cb05
Fix transaction submission within hydra
reeshavacharya May 22, 2025
0ea7dad
Pass TxModal instead of creating json object
reeshavacharya May 23, 2025
87c67b6
Fix Either monad wrapper in response
reeshavacharya May 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
/.cluster
# vscode related
.vscode
.history
.history
.env
4 changes: 2 additions & 2 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-- See CONTRIBUTING for information about these, including some Nix commands
-- you need to run if you change them
index-state:
, hackage.haskell.org 2025-01-06T06:07:18Z
, cardano-haskell-packages 2025-01-04T13:50:25Z
, hackage.haskell.org 2025-05-07T06:09:46Z
, cardano-haskell-packages 2025-04-29T20:52:57Z

-- Custom repository for cardano haskell packages, see CONTRIBUTING for more
repository cardano-haskell-packages
Expand Down
49 changes: 49 additions & 0 deletions docs/docs/scenarioTests.md
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.) [] ..."
}
}
```
4 changes: 0 additions & 4 deletions hydra/sequence-diagrams/Readme.md

This file was deleted.

Binary file removed hydra/sequence-diagrams/abort.jpg
Binary file not shown.
Binary file removed hydra/sequence-diagrams/close.jpg
Binary file not shown.
Binary file removed hydra/sequence-diagrams/commit.jpg
Binary file not shown.
Binary file removed hydra/sequence-diagrams/decommit.jpg
Binary file not shown.
Binary file removed hydra/sequence-diagrams/fanout.jpg
Binary file not shown.
Binary file removed hydra/sequence-diagrams/hydra-architecture.png
Binary file not shown.
Binary file removed hydra/sequence-diagrams/init.jpg
Binary file not shown.
Binary file removed hydra/sequence-diagrams/protocol-parameters.jpg
Binary file not shown.
Binary file removed hydra/sequence-diagrams/utxo.jpg
Binary file not shown.
254 changes: 254 additions & 0 deletions kuber-hydra/app/Api/Spec.hs
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)
27 changes: 23 additions & 4 deletions kuber-hydra/app/Main.hs
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"
Copy link
Collaborator

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

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
Loading