Skip to content

Commit

Permalink
Resurrect 0MQ-based mock chain #203
Browse files Browse the repository at this point in the history
  • Loading branch information
abailly-iohk committed Feb 22, 2022
1 parent e83ad6d commit 86e0785
Show file tree
Hide file tree
Showing 20 changed files with 602 additions and 190 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
docker:
strategy:
matrix:
target: [ hydra-node, hydra-tui ]
target: [ hydra-node, hydra-tui, mock-chain ]

runs-on: ubuntu-latest
steps:
Expand Down
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ COPY . .

RUN nix-build -A hydra-node -o hydra-node-result release.nix > hydra-node.drv
RUN nix-build -A hydra-tui -o hydra-tui-result release.nix > hydra-tui.drv
RUN nix-build -A mock-chain -o mock-chain-result release.nix > mock-chain.drv

RUN nix-store --export $(nix-store -qR $(cat hydra-node.drv)) > hydra-node.closure
RUN nix-store --export $(nix-store -qR $(cat hydra-tui.drv)) > hydra-tui.closure
RUN nix-store --export $(nix-store -qR $(cat mock-chain.drv)) > mock-chain.closure

# ------------------------------------------------------------------- HYDRA-NODE

Expand All @@ -43,3 +45,15 @@ RUN nix-store --import < hydra-tui.closure && nix-env -i $(cat hydra-tui.drv)

STOPSIGNAL SIGINT
ENTRYPOINT ["hydra-tui"]

# ------------------------------------------------------------------- MOCK-CHAIN

FROM nixos/nix:2.3.11 as mock-chain

COPY --from=build /build/mock-chain.drv mock-chain.drv
COPY --from=build /build/mock-chain.closure mock-chain.closure

RUN nix-store --import < mock-chain.closure && nix-env -i $(cat mock-chain.drv)

STOPSIGNAL SIGINT
ENTRYPOINT ["mock-chain"]
79 changes: 40 additions & 39 deletions hydra-cluster/bench/Bench/EndToEnd.hs
Original file line number Diff line number Diff line change
Expand Up @@ -97,45 +97,46 @@ bench timeoutSeconds workDir dataset clusterSize =
let cardanoKeys = map (\Dataset{signingKey} -> (getVerificationKey signingKey, signingKey)) dataset
config <- newNodeConfig workDir
withOSStats workDir $
withBFTNode (contramap FromCluster tracer) config (fst <$> cardanoKeys) $ \(RunningNode _ nodeSocket) -> do
withHydraCluster tracer workDir nodeSocket cardanoKeys $ \(leader :| followers) -> do
let nodes = leader : followers
waitForNodesConnected tracer [1 .. fromIntegral clusterSize] nodes

initialUTxOs <- createUTxOToCommit dataset nodeSocket

let contestationPeriod = 10 :: Natural
send leader $ input "Init" ["contestationPeriod" .= contestationPeriod]
let parties = Set.fromList $ map (deriveParty . generateKey) [1 .. fromIntegral clusterSize]
waitFor tracer (fromIntegral $ 10 * clusterSize) nodes $
output "ReadyToCommit" ["parties" .= parties]

expectedUTxO <- mconcat <$> forM (zip nodes initialUTxOs) (uncurry commit)

waitFor tracer (fromIntegral $ 10 * clusterSize) nodes $
output "HeadIsOpen" ["utxo" .= expectedUTxO]

processedTransactions <- processTransactions nodes dataset

putTextLn "Closing the Head..."
send leader $ input "Close" []
waitMatch (fromIntegral $ 60 * clusterSize) leader $ \v ->
guard (v ^? key "tag" == Just "HeadIsFinalized")

let res = mapMaybe analyze . Map.toList $ processedTransactions
aggregates = movingAverage res

writeResultsCsv (workDir </> "results.csv") aggregates

-- TODO: Create a proper summary
let confTimes = map (\(_, _, a) -> a) res
below1Sec = filter (< 1) confTimes
avgConfirmation = double (nominalDiffTimeToSeconds $ sum confTimes) / double (length confTimes)
percentBelow1Sec = double (length below1Sec) / double (length confTimes) * 100
putTextLn $ "Confirmed txs: " <> show (length confTimes)
putTextLn $ "Average confirmation time: " <> show avgConfirmation
putTextLn $ "Confirmed below 1 sec: " <> show percentBelow1Sec <> "%"
percentBelow1Sec `shouldSatisfy` (> 90)
withBFTNode (contramap FromCluster tracer) config (fst <$> cardanoKeys) $
\(RunningNode _ nodeSocket) -> do
withHydraCluster tracer workDir nodeSocket cardanoKeys $ \(leader :| followers) -> do
let nodes = leader : followers
waitForNodesConnected tracer nodes

initialUTxOs <- createUTxOToCommit dataset nodeSocket

let contestationPeriod = 10 :: Natural
send leader $ input "Init" ["contestationPeriod" .= contestationPeriod]
let parties = Set.fromList $ map (deriveParty . generateKey) [1 .. fromIntegral clusterSize]
waitFor tracer (fromIntegral $ 10 * clusterSize) nodes $
output "ReadyToCommit" ["parties" .= parties]

expectedUTxO <- mconcat <$> forM (zip nodes initialUTxOs) (uncurry commit)

waitFor tracer (fromIntegral $ 10 * clusterSize) nodes $
output "HeadIsOpen" ["utxo" .= expectedUTxO]

processedTransactions <- processTransactions nodes dataset

putTextLn "Closing the Head..."
send leader $ input "Close" []
waitMatch (fromIntegral $ 60 * clusterSize) leader $ \v ->
guard (v ^? key "tag" == Just "HeadIsFinalized")

let res = mapMaybe analyze . Map.toList $ processedTransactions
aggregates = movingAverage res

writeResultsCsv (workDir </> "results.csv") aggregates

-- TODO: Create a proper summary
let confTimes = map (\(_, _, a) -> a) res
below1Sec = filter (< 1) confTimes
avgConfirmation = double (nominalDiffTimeToSeconds $ sum confTimes) / double (length confTimes)
percentBelow1Sec = double (length below1Sec) / double (length confTimes) * 100
putTextLn $ "Confirmed txs: " <> show (length confTimes)
putTextLn $ "Average confirmation time: " <> show avgConfirmation
putTextLn $ "Confirmed below 1 sec: " <> show percentBelow1Sec <> "%"
percentBelow1Sec `shouldSatisfy` (> 90)

-- | Collect OS-level stats while running some 'IO' action.
--
Expand Down
2 changes: 1 addition & 1 deletion hydra-cluster/bench/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ benchOptions =
( fullDesc
<> progDesc
"Starts a cluster of Hydra nodes interconnected through a network and \
\ talking to a local cardano devnet, generates an initial UTxO set and a bunch \
\ talking to mock-chain, generates an initial UTxO set and a bunch \
\ of valid transactions, and send those transactions to the cluster as \
\ fast as possible.\n \
\ Arguments can control various parameters of the run, like number of nodes, \
Expand Down
6 changes: 4 additions & 2 deletions hydra-cluster/hydra-cluster.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ test-suite integration

build-tool-depends:
hspec-discover:hspec-discover -any, hydra-node:hydra-node -any,
cardano-node:cardano-node -any, cardano-cli:cardano-cli -any
hydra-node:mock-chain -any, cardano-node:cardano-node -any,
cardano-cli:cardano-cli -any

ghc-options: -threaded -rtsopts

Expand Down Expand Up @@ -284,6 +285,7 @@ benchmark bench-e2e
, time

build-tool-depends:
hydra-node:hydra-node -any, cardano-node:cardano-node -any
hydra-node:hydra-node -any, hydra-node:mock-chain -any,
cardano-node:cardano-node -any, cardano-cli:cardano-cli -any

ghc-options: -threaded -rtsopts
57 changes: 37 additions & 20 deletions hydra-cluster/src/HydraNode.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module HydraNode (
getMetrics,
queryNode,
defaultArguments,
withMockChain,
hydraNodeProcess,
module System.Process,
waitForNodesConnected,
Expand Down Expand Up @@ -60,6 +61,7 @@ import System.Process (
)
import System.Timeout (timeout)
import Test.Hydra.Prelude (checkProcessHasNotDied, failAfter, failure, withFile')
import Test.Network.Ports (randomUnusedTCPPorts)

data HydraClient = HydraClient
{ hydraNodeId :: Int
Expand Down Expand Up @@ -311,24 +313,39 @@ defaultArguments nodeId cardanoSKey cardanoVKeys hydraSKey hydraVKeys nodeSocket
<> ["--network-magic", "42"]
<> ["--node-socket", nodeSocket]

waitForNodesConnected :: HasCallStack => Tracer IO EndToEndLog -> [Int] -> [HydraClient] -> IO ()
waitForNodesConnected tracer allNodeIds = mapM_ (waitForNodeConnected tracer allNodeIds)
waitForNodesConnected :: HasCallStack => Tracer IO EndToEndLog -> [HydraClient] -> IO ()
waitForNodesConnected tracer clients =
mapM_ waitForNodeConnected clients
where
allNodeIds = hydraNodeId <$> clients
waitForNodeConnected n@HydraClient{hydraNodeId} =
waitForAll tracer (fromIntegral $ 20 * length allNodeIds) [n] $
fmap
( \nodeId ->
object
[ "tag" .= String "PeerConnected"
, "peer"
.= object
[ "hostname" .= ("127.0.0.1" :: Text)
, "port" .= (5000 + nodeId)
]
]
)
(filter (/= hydraNodeId) allNodeIds)

waitForNodeConnected :: HasCallStack => Tracer IO EndToEndLog -> [Int] -> HydraClient -> IO ()
waitForNodeConnected tracer allNodeIds n@HydraClient{hydraNodeId} =
-- HACK(AB): This is gross, we hijack the node ids and because we know
-- keys are just integers we can compute them but that's ugly -> use property
-- party identifiers everywhere
waitForAll tracer (fromIntegral $ 20 * length allNodeIds) [n] $
fmap
( \party ->
object
[ "tag" .= String "PeerConnected"
, "peer"
.= object
[ "hostname" .= ("127.0.0.1" :: Text)
, "port" .= (5000 + party)
]
]
)
(filter (/= hydraNodeId) allNodeIds)
withMockChain :: ((Int, Int, Int) -> IO ()) -> IO ()
withMockChain action = do
[sync, catchUp, post] <- randomUnusedTCPPorts 3
withCreateProcess (proc "mock-chain" (arguments sync catchUp post)) $
\_in _out _err processHandle -> do
race_ (checkProcessHasNotDied "mock-chain" processHandle) (action (sync, catchUp, post))
where
arguments s c p =
[ "--quiet"
, "--sync-address"
, "tcp://127.0.0.1:" <> show s
, "--catch-up-address"
, "tcp://127.0.0.1:" <> show c
, "--post-address"
, "tcp://127.0.0.1:" <> show p
]
4 changes: 2 additions & 2 deletions hydra-cluster/test/Test/EndToEndSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ spec = around showLogsOnFailure $
withHydraNode tracer aliceSkPath [bobVkPath, carolVkPath] tmpDir nodeSocket 1 aliceSk [bobVk, carolVk] allNodeIds $ \n1 ->
withHydraNode tracer bobSkPath [aliceVkPath, carolVkPath] tmpDir nodeSocket 2 bobSk [aliceVk, carolVk] allNodeIds $ \n2 ->
withHydraNode tracer carolSkPath [aliceVkPath, bobVkPath] tmpDir nodeSocket 3 carolSk [aliceVk, bobVk] allNodeIds $ \n3 -> do
waitForNodesConnected tracer allNodeIds [n1, n2, n3]
waitForNodesConnected tracer [n1, n2, n3]
postSeedPayment defaultNetworkId pparams availableInitialFunds nodeSocket aliceCardanoSk 100_000_000
postSeedPayment defaultNetworkId pparams availableInitialFunds nodeSocket bobCardanoSk 100_000_000
postSeedPayment defaultNetworkId pparams availableInitialFunds nodeSocket carolCardanoSk 100_000_000
Expand Down Expand Up @@ -183,7 +183,7 @@ spec = around showLogsOnFailure $
withHydraNode tracer bobSkPath [aliceVkPath, carolVkPath] tmpDir nodeSocket 2 bobSk [aliceVk, carolVk] allNodeIds $ \_ ->
withHydraNode tracer carolSkPath [aliceVkPath, bobVkPath] tmpDir nodeSocket 3 carolSk [aliceVk, bobVk] allNodeIds $ \_ -> do
postSeedPayment defaultNetworkId pparams availableInitialFunds nodeSocket aliceCardanoSk 100_000_000
waitForNodesConnected tracer allNodeIds [n1]
waitForNodesConnected tracer [n1]
send n1 $ input "Init" ["contestationPeriod" .= int 10]
waitFor tracer 3 [n1] $ output "ReadyToCommit" ["parties" .= Set.fromList [alice, bob, carol]]
metrics <- getMetrics n1
Expand Down
35 changes: 18 additions & 17 deletions hydra-node/exe/hydra-node/Main.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE TypeApplications #-}

module Main where

Expand All @@ -9,6 +8,7 @@ import Hydra.API.Server (withAPIServer)
import Hydra.Chain (Chain, ChainCallback)
import Hydra.Chain.Direct (withDirectChain)
import Hydra.Chain.Direct.Util (readKeyPair, readVerificationKey)
import Hydra.Chain.ZeroMQ (withMockChain)
import Hydra.HeadLogic (Environment (..), Event (..))
import Hydra.Ledger.Cardano (Tx)
import qualified Hydra.Ledger.Cardano as Ledger
Expand Down Expand Up @@ -51,22 +51,23 @@ withChain ::
ChainConfig ->
(Chain Tx IO -> IO ()) ->
IO ()
withChain tracer party callback config action = do
keyPair@(vk, _) <- readKeyPair cardanoSigningKey
otherCardanoKeys <- mapM readVerificationKey cardanoVerificationKeys
withIOManager $ \iocp -> do
withDirectChain
(contramap DirectChain tracer)
networkMagic
iocp
nodeSocket
keyPair
party
(vk : otherCardanoKeys)
callback
action
where
DirectChainConfig{networkMagic, nodeSocket, cardanoSigningKey, cardanoVerificationKeys} = config
withChain tracer party callback config action = case config of
MockChainConfig mockChain ->
withMockChain (contramap MockChain tracer) mockChain callback action
DirectChainConfig{networkMagic, nodeSocket, cardanoSigningKey, cardanoVerificationKeys} -> do
keyPair@(vk, _) <- readKeyPair cardanoSigningKey
otherCardanoKeys <- mapM readVerificationKey cardanoVerificationKeys
withIOManager $ \iocp -> do
withDirectChain
(contramap DirectChain tracer)
networkMagic
iocp
nodeSocket
keyPair
party
(vk : otherCardanoKeys)
callback
action

identifyNode :: Options -> Options
identifyNode opt@Options{verbosity = Verbose "HydraNode", nodeId} = opt{verbosity = Verbose $ "HydraNode-" <> show nodeId}
Expand Down
Loading

0 comments on commit 86e0785

Please sign in to comment.