From fe73fd177cbbf2a3de6ac19277c1c3a4f5001c35 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 6 Aug 2024 17:29:07 +0200 Subject: [PATCH 001/115] Define basic demo options --- hydra-cluster/bench/Bench/Options.hs | 27 +++++++++++++++++++++++++++ hydra-cluster/bench/Main.hs | 2 ++ 2 files changed, 29 insertions(+) diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index d5d84e3688c..8e453a1695a 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -2,6 +2,8 @@ module Bench.Options where import Hydra.Prelude +import Hydra.Cardano.Api (SocketPath) +import Hydra.Options (nodeSocketParser) import Options.Applicative ( Parser, ParserInfo, @@ -41,6 +43,13 @@ data Options , timeoutSeconds :: NominalDiffTime , startingNodeId :: Int } + | DemoOptions + { outputDirectory :: Maybe FilePath + , scalingFactor :: Int + , timeoutSeconds :: NominalDiffTime + , startingNodeId :: Int + , nodeSocket :: Maybe SocketPath + } benchOptionsParser :: ParserInfo Options benchOptionsParser = @@ -48,6 +57,7 @@ benchOptionsParser = ( hsubparser ( command "single" standaloneOptionsInfo <> command "datasets" datasetOptionsInfo + <> command "demo" demoOptionsInfo ) <**> helper ) @@ -156,6 +166,23 @@ startingNodeIdParser = \ benchmark conflicts with default ports allocation scheme (default: 0)" ) +demoOptionsInfo :: ParserInfo Options +demoOptionsInfo = + info + demoOptionsParser + ( progDesc + "Run scenarios from local runnign demo." + ) + +demoOptionsParser :: Parser Options +demoOptionsParser = + DemoOptions + <$> optional outputDirectoryParser + <*> scalingFactorParser + <*> timeoutParser + <*> startingNodeIdParser + <*> optional nodeSocketParser + datasetOptionsInfo :: ParserInfo Options datasetOptionsInfo = info diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index e9a10ac60bc..d48d9ef263f 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -33,6 +33,8 @@ main = play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do run outputDirectory timeoutSeconds startingNodeId datasetFiles + DemoOptions{} -> do + pure () where play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do putStrLn $ "Generating single dataset in work directory: " <> workDir From 86201c0f06f24323f7b9433ed7766a51c0b9a1a5 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 6 Aug 2024 18:02:05 +0200 Subject: [PATCH 002/115] Extract genDatasetConstantUTxO To take faucet and client keys as arg, so that is reusable also for the current use-case. --- hydra-cluster/src/Hydra/Generator.hs | 21 ++++++++++++--------- hydra-cluster/test/Test/GeneratorSpec.hs | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index c1c9b6eaf2f..181d87182c0 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -32,8 +32,10 @@ data Dataset = Dataset instance Arbitrary Dataset where arbitrary = sized $ \n -> do - sk <- genSigningKey - genDatasetConstantUTxO sk (n `div` 10) n + faucetSk <- genSigningKey + let nClients = n `div` 10 + let clientKeys = replicate nClients (generateWith arbitrary 42) + genDatasetConstantUTxO faucetSk clientKeys n data ClientKeys = ClientKeys { signingKey :: SigningKey PaymentKey @@ -87,22 +89,23 @@ generateConstantUTxODataset :: IO Dataset generateConstantUTxODataset nClients nTxs = do (_, faucetSk) <- keysFor Faucet - generate $ genDatasetConstantUTxO faucetSk nClients nTxs + let clientKeys = replicate nClients (generateWith arbitrary 42) + generate $ genDatasetConstantUTxO faucetSk clientKeys nTxs genDatasetConstantUTxO :: -- | The faucet signing key SigningKey PaymentKey -> - -- | Number of clients - Int -> + -- | Clients + [ClientKeys] -> -- | Number of transactions Int -> Gen Dataset -genDatasetConstantUTxO faucetSk nClients nTxs = do - clientKeys <- replicateM nClients arbitrary +genDatasetConstantUTxO faucetSk allClientKeys nTxs = do + let nClients = length allClientKeys -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the benchmark run. - clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do + clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) pure (getVerificationKey externalSigningKey, amount) let fundingTransaction = @@ -111,7 +114,7 @@ genDatasetConstantUTxO faucetSk nClients nTxs = do faucetSk (Coin availableInitialFunds) clientFunds - clientDatasets <- forM clientKeys (generateClientDataset fundingTransaction) + clientDatasets <- forM allClientKeys (generateClientDataset fundingTransaction) pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index f6418177816..931eff31a88 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -38,14 +38,14 @@ spec = parallel $ do prop_keepsUTxOConstant :: Property prop_keepsUTxOConstant = - forAll arbitrary $ \(Positive n) -> do + forAll arbitrary $ \(Positive n, clientKeys) -> do idempotentIOProperty $ do faucetSk <- snd <$> keysFor Faucet let ledgerEnv = newLedgerEnv defaultPParams -- XXX: non-exhaustive pattern match pure $ - forAll (genDatasetConstantUTxO faucetSk 1 n) $ + forAll (genDatasetConstantUTxO faucetSk clientKeys n) $ \Dataset{fundingTransaction, clientDatasets = [ClientDataset{txSequence}]} -> let initialUTxO = utxoFromTx fundingTransaction finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence From ffaf84ca0966f0269dd43d5578ba9e3a4bc057b4 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 6 Aug 2024 18:25:31 +0200 Subject: [PATCH 003/115] Extract bench scenario So that is also reusable for the current use-case. --- hydra-cluster/bench/Bench/EndToEnd.hs | 121 ++++++++++++++------------ 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 3fc2cd2fd31..70e85486a2b 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -40,9 +40,10 @@ import Hydra.Crypto (generateSigningKey) import Hydra.Generator (ClientDataset (..), ClientKeys (..), Dataset (..)) import Hydra.Ledger (txId) import Hydra.Logging (Tracer, withTracerOutputTo) -import Hydra.Party (deriveParty) +import Hydra.Party (Party, deriveParty) import HydraNode ( HydraClient, + HydraNodeLog, hydraNodeId, input, output, @@ -77,7 +78,7 @@ data Event = Event deriving anyclass (ToJSON) bench :: Int -> NominalDiffTime -> FilePath -> Dataset -> IO Summary -bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets, title, description} = do +bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets} = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> @@ -86,7 +87,6 @@ bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets, titl let cardanoKeys = map (\ClientDataset{clientKeys = ClientKeys{signingKey}} -> (getVerificationKey signingKey, signingKey)) clientDatasets let hydraKeys = generateSigningKey . show <$> [1 .. toInteger (length cardanoKeys)] let parties = Set.fromList (deriveParty <$> hydraKeys) - let clusterSize = fromIntegral $ length clientDatasets withOSStats workDir $ withCardanoNodeDevnet (contramap FromCardanoNode tracer) workDir $ \node@RunningNode{nodeSocket} -> do putTextLn "Seeding network" @@ -95,57 +95,70 @@ bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets, titl let contestationPeriod = UnsafeContestationPeriod 10 putStrLn $ "Starting hydra cluster in " <> workDir withHydraCluster hydraTracer workDir nodeSocket startingNodeId cardanoKeys hydraKeys hydraScriptsTxId contestationPeriod $ \(leader :| followers) -> do - let clients = leader : followers - waitForNodesConnected hydraTracer 20 clients - - putTextLn "Initializing Head" - send leader $ input "Init" [] - headId <- - waitForAllMatch (fromIntegral $ 10 * clusterSize) clients $ - headIsInitializingWith parties - - putTextLn "Comitting initialUTxO from dataset" - expectedUTxO <- commitUTxO node clients dataset - - waitFor hydraTracer (fromIntegral $ 10 * clusterSize) clients $ - output "HeadIsOpen" ["utxo" .= expectedUTxO, "headId" .= headId] - - putTextLn "HeadIsOpen" - processedTransactions <- processTransactions clients dataset - - putTextLn "Closing the Head" - send leader $ input "Close" [] - - deadline <- waitMatch 300 leader $ \v -> do - guard $ v ^? key "tag" == Just "HeadIsClosed" - guard $ v ^? key "headId" == Just (toJSON headId) - v ^? key "contestationDeadline" . _JSON - - -- Expect to see ReadyToFanout within 3 seconds after deadline - remainingTime <- diffUTCTime deadline <$> getCurrentTime - waitFor hydraTracer (remainingTime + 3) [leader] $ - output "ReadyToFanout" ["headId" .= headId] - - putTextLn "Finalizing the Head" - send leader $ input "Fanout" [] - waitMatch 100 leader $ \v -> do - guard (v ^? key "tag" == Just "HeadIsFinalized") - guard $ v ^? key "headId" == Just (toJSON headId) - - let res = mapMaybe analyze . Map.toList $ processedTransactions - aggregates = movingAverage res - - writeResultsCsv (workDir "results.csv") aggregates - - let confTimes = map (\(_, _, a) -> a) res - numberOfTxs = length confTimes - numberOfInvalidTxs = length $ Map.filter (isJust . invalidAt) processedTransactions - averageConfirmationTime = sum confTimes / fromIntegral numberOfTxs - quantiles = makeQuantiles confTimes - summaryTitle = fromMaybe "Baseline Scenario" title - summaryDescription = fromMaybe defaultDescription description - - pure $ Summary{clusterSize, numberOfTxs, averageConfirmationTime, quantiles, summaryTitle, summaryDescription, numberOfInvalidTxs} + scenario hydraTracer node workDir dataset parties leader followers + +scenario :: + Tracer IO HydraNodeLog -> + RunningNode -> + FilePath -> + Dataset -> + Set Party -> + HydraClient -> + [HydraClient] -> + IO Summary +scenario hydraTracer node workDir dataset@Dataset{clientDatasets, title, description} parties leader followers = do + let clusterSize = fromIntegral $ length clientDatasets + let clients = leader : followers + waitForNodesConnected hydraTracer 20 clients + + putTextLn "Initializing Head" + send leader $ input "Init" [] + headId <- + waitForAllMatch (fromIntegral $ 10 * clusterSize) clients $ + headIsInitializingWith parties + + putTextLn "Comitting initialUTxO from dataset" + expectedUTxO <- commitUTxO node clients dataset + + waitFor hydraTracer (fromIntegral $ 10 * clusterSize) clients $ + output "HeadIsOpen" ["utxo" .= expectedUTxO, "headId" .= headId] + + putTextLn "HeadIsOpen" + processedTransactions <- processTransactions clients dataset + + putTextLn "Closing the Head" + send leader $ input "Close" [] + + deadline <- waitMatch 300 leader $ \v -> do + guard $ v ^? key "tag" == Just "HeadIsClosed" + guard $ v ^? key "headId" == Just (toJSON headId) + v ^? key "contestationDeadline" . _JSON + + -- Expect to see ReadyToFanout within 3 seconds after deadline + remainingTime <- diffUTCTime deadline <$> getCurrentTime + waitFor hydraTracer (remainingTime + 3) [leader] $ + output "ReadyToFanout" ["headId" .= headId] + + putTextLn "Finalizing the Head" + send leader $ input "Fanout" [] + waitMatch 100 leader $ \v -> do + guard (v ^? key "tag" == Just "HeadIsFinalized") + guard $ v ^? key "headId" == Just (toJSON headId) + + let res = mapMaybe analyze . Map.toList $ processedTransactions + aggregates = movingAverage res + + writeResultsCsv (workDir "results.csv") aggregates + + let confTimes = map (\(_, _, a) -> a) res + numberOfTxs = length confTimes + numberOfInvalidTxs = length $ Map.filter (isJust . invalidAt) processedTransactions + averageConfirmationTime = sum confTimes / fromIntegral numberOfTxs + quantiles = makeQuantiles confTimes + summaryTitle = fromMaybe "Baseline Scenario" title + summaryDescription = fromMaybe defaultDescription description + + pure $ Summary{clusterSize, numberOfTxs, averageConfirmationTime, quantiles, summaryTitle, summaryDescription, numberOfInvalidTxs} defaultDescription :: Text defaultDescription = "" From 74ad55390d88ba070a9502b9c5eeab5bfee495c2 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 6 Aug 2024 18:35:09 +0200 Subject: [PATCH 004/115] Extract findRunningCardanoNode' To find a node runing on unknown network, like devnet. So that is also reusable for the current use-case. --- hydra-cluster/src/CardanoNode.hs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/hydra-cluster/src/CardanoNode.hs b/hydra-cluster/src/CardanoNode.hs index 1a0bc1da22f..2251ca7710f 100644 --- a/hydra-cluster/src/CardanoNode.hs +++ b/hydra-cluster/src/CardanoNode.hs @@ -132,26 +132,30 @@ getCardanoNodeVersion = -- by 'defaultCardanoNodeArgs'. findRunningCardanoNode :: Tracer IO NodeLog -> FilePath -> KnownNetwork -> IO (Maybe RunningNode) findRunningCardanoNode tracer workDir knownNetwork = do - try (queryGenesisParameters knownNetworkId socketPath QueryTip) >>= \case + findRunningCardanoNode' tracer knownNetworkId socketPath + where + knownNetworkId = toNetworkId knownNetwork + + socketPath = File $ workDir nodeSocket + + CardanoNodeArgs{nodeSocket} = defaultCardanoNodeArgs + +findRunningCardanoNode' :: Tracer IO NodeLog -> NetworkId -> SocketPath -> IO (Maybe RunningNode) +findRunningCardanoNode' tracer networkId nodeSocket = do + try (queryGenesisParameters networkId nodeSocket QueryTip) >>= \case Left (e :: SomeException) -> traceWith tracer MsgQueryGenesisParametersFailed{err = show e} $> Nothing Right GenesisParameters{protocolParamActiveSlotsCoefficient, protocolParamSlotLength} -> pure $ Just RunningNode - { networkId = knownNetworkId - , nodeSocket = socketPath + { networkId + , nodeSocket , blockTime = computeBlockTime protocolParamSlotLength protocolParamActiveSlotsCoefficient } - where - knownNetworkId = toNetworkId knownNetwork - - socketPath = File $ workDir nodeSocket - - CardanoNodeArgs{nodeSocket} = defaultCardanoNodeArgs -- | Start a single cardano-node devnet using the config from config/ and -- credentials from config/credentials/. Only the 'Faucet' actor will receive From 8d1eb68c4cf25866b0325e9045a42c7213ebe061 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 6 Aug 2024 19:06:53 +0200 Subject: [PATCH 005/115] Add network id as part of the options Also removed starting node id as not needed, because Alice will always lead the demo bench. --- hydra-cluster/bench/Bench/Options.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index 8e453a1695a..fd6b52cfa9e 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -2,8 +2,8 @@ module Bench.Options where import Hydra.Prelude -import Hydra.Cardano.Api (SocketPath) -import Hydra.Options (nodeSocketParser) +import Hydra.Cardano.Api (NetworkId, SocketPath) +import Hydra.Options (networkIdParser, nodeSocketParser) import Options.Applicative ( Parser, ParserInfo, @@ -47,8 +47,8 @@ data Options { outputDirectory :: Maybe FilePath , scalingFactor :: Int , timeoutSeconds :: NominalDiffTime - , startingNodeId :: Int - , nodeSocket :: Maybe SocketPath + , networkId :: NetworkId + , nodeSocket :: SocketPath } benchOptionsParser :: ParserInfo Options @@ -180,8 +180,8 @@ demoOptionsParser = <$> optional outputDirectoryParser <*> scalingFactorParser <*> timeoutParser - <*> startingNodeIdParser - <*> optional nodeSocketParser + <*> networkIdParser + <*> nodeSocketParser datasetOptionsInfo :: ParserInfo Options datasetOptionsInfo = From 85667fd0a59f4f3e018f4204ac9bc4798a0c9ef7 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 6 Aug 2024 19:10:37 +0200 Subject: [PATCH 006/115] Draft base bench demo script --- hydra-cluster/bench/Bench/EndToEnd.hs | 62 +++++++++++++++++++++------ hydra-cluster/bench/Main.hs | 45 +++++++++++++++---- 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 70e85486a2b..7d9bd2ac0b1 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -7,7 +7,7 @@ import Test.Hydra.Prelude import Bench.Summary (Summary (..), makeQuantiles) import CardanoClient (RunningNode (..), awaitTransaction, submitTransaction, submitTx) -import CardanoNode (withCardanoNodeDevnet) +import CardanoNode (findRunningCardanoNode', withCardanoNodeDevnet) import Control.Concurrent.Class.MonadSTM ( MonadSTM (readTVarIO), check, @@ -28,7 +28,7 @@ import Data.Scientific (Scientific) import Data.Set ((\\)) import Data.Set qualified as Set import Data.Time (UTCTime (UTCTime), utctDayTime) -import Hydra.Cardano.Api (Tx, TxId, UTxO, getVerificationKey, signTx) +import Hydra.Cardano.Api (NetworkId, SocketPath, Tx, TxId, UTxO, getVerificationKey, signTx) import Hydra.Cluster.Faucet (FaucetLog, publishHydraScriptsAs, seedFromFaucet) import Hydra.Cluster.Fixture (Actor (Faucet)) import Hydra.Cluster.Scenarios ( @@ -53,6 +53,7 @@ import HydraNode ( waitForAllMatch, waitForNodesConnected, waitMatch, + withConnectionToNode, withHydraCluster, ) import System.Directory (findExecutable) @@ -97,6 +98,37 @@ bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets} = do withHydraCluster hydraTracer workDir nodeSocket startingNodeId cardanoKeys hydraKeys hydraScriptsTxId contestationPeriod $ \(leader :| followers) -> do scenario hydraTracer node workDir dataset parties leader followers +benchDemo :: + NetworkId -> + SocketPath -> + NominalDiffTime -> + FilePath -> + Dataset -> + IO Summary +benchDemo networkId nodeSocket timeoutSeconds workDir dataset@Dataset{clientDatasets, fundingTransaction} = do + putStrLn $ "Test logs available in: " <> (workDir "test.log") + withFile (workDir "test.log") ReadWriteMode $ \hdl -> + withTracerOutputTo hdl "Test" $ \tracer -> + failAfter timeoutSeconds $ do + putTextLn "Starting benchmark" + findRunningCardanoNode' (contramap FromCardanoNode tracer) networkId nodeSocket >>= \case + Nothing -> + error ("Not found running node at socket: " <> show nodeSocket <> ", and network: " <> show networkId) + Just node -> do + putTextLn "Seeding network" + fundClients networkId nodeSocket fundingTransaction + forM_ clientDatasets (fuelWith100Ada (contramap FromFaucet tracer) node) + putStrLn $ "Connecting to hydra cluster in " <> workDir + let hydraTracer = contramap FromHydraNode tracer + let cardanoKeys = map (\ClientDataset{clientKeys = ClientKeys{signingKey}} -> (getVerificationKey signingKey, signingKey)) clientDatasets + let hydraKeys = generateSigningKey . show <$> [1 .. toInteger (length cardanoKeys)] + let parties = Set.fromList (deriveParty <$> hydraKeys) + withConnectionToNode hydraTracer 1 $ \leader -> + withConnectionToNode hydraTracer 2 $ \node2 -> + withConnectionToNode hydraTracer 3 $ \node3 -> do + let followers = [node2, node3] + scenario hydraTracer node workDir dataset parties leader followers + scenario :: Tracer IO HydraNodeLog -> RunningNode -> @@ -251,20 +283,22 @@ movingAverage confirmations = -- transaction is returned. seedNetwork :: RunningNode -> Dataset -> Tracer IO FaucetLog -> IO TxId seedNetwork node@RunningNode{nodeSocket, networkId} Dataset{fundingTransaction, clientDatasets} tracer = do - fundClients - forM_ clientDatasets fuelWith100Ada + fundClients networkId nodeSocket fundingTransaction + forM_ clientDatasets (fuelWith100Ada tracer node) putTextLn "Publishing hydra scripts" publishHydraScriptsAs node Faucet - where - fundClients = do - putTextLn "Fund scenario from faucet" - submitTransaction networkId nodeSocket fundingTransaction - void $ awaitTransaction networkId nodeSocket fundingTransaction - - fuelWith100Ada ClientDataset{clientKeys = ClientKeys{signingKey}} = do - let vk = getVerificationKey signingKey - putTextLn $ "Seed client " <> show vk - seedFromFaucet node vk 100_000_000 tracer + +fundClients :: NetworkId -> SocketPath -> Tx -> IO () +fundClients networkId nodeSocket fundingTransaction = do + putTextLn "Fund scenario from faucet" + submitTransaction networkId nodeSocket fundingTransaction + void $ awaitTransaction networkId nodeSocket fundingTransaction + +fuelWith100Ada :: Tracer IO FaucetLog -> RunningNode -> ClientDataset -> IO UTxO +fuelWith100Ada tracer node ClientDataset{clientKeys = ClientKeys{signingKey}} = do + let vk = getVerificationKey signingKey + putTextLn $ "Seed client " <> show vk + seedFromFaucet node vk 100_000_000 tracer -- | Commit all (expected to exit) 'initialUTxO' from the dataset using the -- (asumed same sequence) of clients. diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index d48d9ef263f..63966f3c743 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -5,11 +5,13 @@ module Main where import Hydra.Prelude import Test.Hydra.Prelude -import Bench.EndToEnd (bench) +import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) -import Hydra.Generator (Dataset (..), generateConstantUTxODataset) +import Hydra.Cluster.Fixture (Actor (..)) +import Hydra.Cluster.Util (keysFor) +import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxO, generateConstantUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) @@ -32,24 +34,49 @@ main = workDir <- createSystemTempDirectory "bench" play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do - run outputDirectory timeoutSeconds startingNodeId datasetFiles - DemoOptions{} -> do - pure () + let action = bench startingNodeId timeoutSeconds + run outputDirectory datasetFiles action + DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket} -> do + workDir <- createSystemTempDirectory "demo-bench" + (_, faucetSk) <- keysFor Faucet + clientKeys <- do + aliceSk <- snd <$> keysFor Alice + aliceFundsSk <- snd <$> keysFor AliceFunds + bobSk <- snd <$> keysFor Bob + bobFundsSk <- snd <$> keysFor BobFunds + carolSk <- snd <$> keysFor Carol + carolFundsSk <- snd <$> keysFor CarolFunds + let alice = ClientKeys aliceSk aliceFundsSk + bob = ClientKeys bobSk bobFundsSk + carol = ClientKeys carolSk carolFundsSk + pure [alice, bob, carol] + playDemo outputDirectory timeoutSeconds scalingFactor faucetSk clientKeys workDir networkId nodeSocket where + playDemo outputDirectory timeoutSeconds scalingFactor faucetSk clientKeys workDir networkId nodeSocket = do + putStrLn $ "Generating single dataset in work directory: " <> workDir + numberOfTxs <- generate $ scale (* scalingFactor) getSize + dataset <- generate $ genDatasetConstantUTxO faucetSk clientKeys numberOfTxs + let datasetPath = workDir "dataset.json" + saveDataset datasetPath dataset + let action = benchDemo networkId nodeSocket timeoutSeconds + run outputDirectory [datasetPath] action + play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize dataset <- generateConstantUTxODataset (fromIntegral clusterSize) numberOfTxs let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset - run outputDirectory timeoutSeconds startingNodeId [datasetPath] + let action = bench startingNodeId timeoutSeconds + run outputDirectory [datasetPath] action replay outputDirectory timeoutSeconds startingNodeId benchDir = do let datasetPath = benchDir "dataset.json" putStrLn $ "Replaying single dataset from work directory: " <> datasetPath - run outputDirectory timeoutSeconds startingNodeId [datasetPath] + let action = bench startingNodeId timeoutSeconds + run outputDirectory [datasetPath] action - run outputDirectory timeoutSeconds startingNodeId datasetFiles = do + run outputDirectory datasetFiles action = do results <- forM datasetFiles $ \datasetPath -> do putTextLn $ "Running benchmark with dataset " <> show datasetPath dataset <- loadDataset datasetPath @@ -57,7 +84,7 @@ main = withArgs [] $ do -- XXX: Wait between each bench run to give the OS time to cleanup resources?? threadDelay 10 - try @_ @HUnitFailure (bench startingNodeId timeoutSeconds dir dataset) >>= \case + try @_ @HUnitFailure (action dir dataset) >>= \case Left exc -> pure $ Left (dataset, dir, TestFailed exc) Right summary@Summary{numberOfInvalidTxs} | numberOfInvalidTxs == 0 -> pure $ Right summary From 22a0dbdbc16e199257448101ffd657eca5862df1 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 8 Aug 2024 13:49:53 +0200 Subject: [PATCH 007/115] Fix seeding devnet on bench-demo given scripts were already published --- hydra-cluster/bench/Bench/Options.hs | 2 +- hydra-cluster/bench/Main.hs | 13 ++-- hydra-cluster/src/CardanoClient.hs | 28 ++++++-- hydra-cluster/src/Hydra/Generator.hs | 82 ++++++++++++++++++------ hydra-cluster/test/Test/GeneratorSpec.hs | 19 +++--- 5 files changed, 104 insertions(+), 40 deletions(-) diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index fd6b52cfa9e..d542739f9e5 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -171,7 +171,7 @@ demoOptionsInfo = info demoOptionsParser ( progDesc - "Run scenarios from local runnign demo." + "Run scenarios from local running demo." ) demoOptionsParser :: Parser Options diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 63966f3c743..fe4ee8f687e 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -11,7 +11,7 @@ import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Util (keysFor) -import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxO, generateConstantUTxODataset) +import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxODemo, generateConstantUTxODataset, getFaucetInitialFunds) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) @@ -38,7 +38,7 @@ main = run outputDirectory datasetFiles action DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket} -> do workDir <- createSystemTempDirectory "demo-bench" - (_, faucetSk) <- keysFor Faucet + (faucetVk, faucetSk) <- keysFor Faucet clientKeys <- do aliceSk <- snd <$> keysFor Alice aliceFundsSk <- snd <$> keysFor AliceFunds @@ -50,14 +50,17 @@ main = bob = ClientKeys bobSk bobFundsSk carol = ClientKeys carolSk carolFundsSk pure [alice, bob, carol] - playDemo outputDirectory timeoutSeconds scalingFactor faucetSk clientKeys workDir networkId nodeSocket + playDemo outputDirectory timeoutSeconds scalingFactor (faucetVk, faucetSk) clientKeys workDir networkId nodeSocket where - playDemo outputDirectory timeoutSeconds scalingFactor faucetSk clientKeys workDir networkId nodeSocket = do + playDemo outputDirectory timeoutSeconds scalingFactor (faucetVk, faucetSk) clientKeys workDir networkId nodeSocket = do putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize - dataset <- generate $ genDatasetConstantUTxO faucetSk clientKeys numberOfTxs + initialFunds <- getFaucetInitialFunds faucetVk nodeSocket + dataset <- generate $ genDatasetConstantUTxODemo faucetSk clientKeys numberOfTxs initialFunds + -- TODO! remove as only needed for dbg let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset + -- let action = benchDemo networkId nodeSocket timeoutSeconds run outputDirectory [datasetPath] action diff --git a/hydra-cluster/src/CardanoClient.hs b/hydra-cluster/src/CardanoClient.hs index 9ad588637ee..cd52a0d3dc9 100644 --- a/hydra-cluster/src/CardanoClient.hs +++ b/hydra-cluster/src/CardanoClient.hs @@ -111,7 +111,7 @@ waitForUTxO node utxo = txOut -> error $ "Unexpected TxOut " <> show txOut -mkGenesisTx :: +mkInitialTx :: NetworkId -> -- | Owner of the 'initialFund'. SigningKey PaymentKey -> @@ -119,17 +119,14 @@ mkGenesisTx :: Coin -> -- | Recipients and amounts to pay in this transaction. [(VerificationKey PaymentKey, Coin)] -> + TxIn -> + -- | Initial input from which to spend Tx -mkGenesisTx networkId signingKey initialAmount recipients = +mkInitialTx networkId signingKey initialAmount recipients initialInput = case buildRaw [initialInput] (recipientOutputs <> [changeOutput]) of Left err -> error $ "Fail to build genesis transations: " <> show err Right tx -> sign signingKey tx where - initialInput = - genesisUTxOPseudoTxIn - networkId - (unsafeCastHash $ verificationKeyHash $ getVerificationKey signingKey) - totalSent = foldMap snd recipients changeAddr = mkVkAddress networkId (getVerificationKey signingKey) @@ -148,6 +145,23 @@ mkGenesisTx networkId signingKey initialAmount recipients = TxOutDatumNone ReferenceScriptNone +mkGenesisTx :: + NetworkId -> + -- | Owner of the 'initialFund'. + SigningKey PaymentKey -> + -- | Amount of initialFunds + Coin -> + -- | Recipients and amounts to pay in this transaction. + [(VerificationKey PaymentKey, Coin)] -> + Tx +mkGenesisTx networkId signingKey initialAmount recipients = + mkInitialTx networkId signingKey initialAmount recipients initialInput + where + initialInput = + genesisUTxOPseudoTxIn + networkId + (unsafeCastHash $ verificationKeyHash $ getVerificationKey signingKey) + data RunningNode = RunningNode { nodeSocket :: SocketPath , networkId :: NetworkId diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 181d87182c0..be70fe60c59 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,14 +5,16 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (mkGenesisTx) +import CardanoClient (mkGenesisTx, mkInitialTx) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) +import Hydra.Chain.CardanoClient (QueryPoint (..), queryUTxOFor) import Hydra.Cluster.Fixture (Actor (Faucet), availableInitialFunds) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger.Cardano (genSigningKey, generateOneTransfer) import Test.QuickCheck (choose, generate, sized) +import Prelude qualified networkId :: NetworkId networkId = Testnet $ NetworkMagic 42 @@ -35,7 +37,8 @@ instance Arbitrary Dataset where faucetSk <- genSigningKey let nClients = n `div` 10 let clientKeys = replicate nClients (generateWith arbitrary 42) - genDatasetConstantUTxO faucetSk clientKeys n + fundingTransaction <- makeGenesisFundingTx faucetSk clientKeys + genDatasetConstantUTxO clientKeys n fundingTransaction data ClientKeys = ClientKeys { signingKey :: SigningKey PaymentKey @@ -89,35 +92,25 @@ generateConstantUTxODataset :: IO Dataset generateConstantUTxODataset nClients nTxs = do (_, faucetSk) <- keysFor Faucet - let clientKeys = replicate nClients (generateWith arbitrary 42) - generate $ genDatasetConstantUTxO faucetSk clientKeys nTxs + clientKeys <- generate $ replicateM nClients arbitrary + fundingTransaction <- generate $ makeGenesisFundingTx faucetSk clientKeys + generate $ genDatasetConstantUTxO clientKeys nTxs fundingTransaction genDatasetConstantUTxO :: - -- | The faucet signing key - SigningKey PaymentKey -> -- | Clients [ClientKeys] -> -- | Number of transactions Int -> + Tx -> Gen Dataset -genDatasetConstantUTxO faucetSk allClientKeys nTxs = do - let nClients = length allClientKeys +genDatasetConstantUTxO allClientKeys nTxs fundingTransaction = do -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the benchmark run. - clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do - amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) - pure (getVerificationKey externalSigningKey, amount) - let fundingTransaction = - mkGenesisTx - networkId - faucetSk - (Coin availableInitialFunds) - clientFunds - clientDatasets <- forM allClientKeys (generateClientDataset fundingTransaction) + clientDatasets <- forM allClientKeys generateClientDataset pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where - generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do + generateClientDataset clientKeys@ClientKeys{externalSigningKey} = do let vk = getVerificationKey externalSigningKey keyPair = (vk, externalSigningKey) -- NOTE: The initialUTxO must all UTXO we will later commit. We assume @@ -133,3 +126,54 @@ genDatasetConstantUTxO faucetSk allClientKeys nTxs = do pure ClientDataset{clientKeys, initialUTxO, txSequence} thrd (_, _, c) = c + +makeGenesisFundingTx :: SigningKey PaymentKey -> [ClientKeys] -> Gen Tx +makeGenesisFundingTx faucetSk clientKeys = do + let nClients = length clientKeys + -- Prepare funding transaction which will give every client's + -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get + -- funded in the beginning of the benchmark run. + clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do + amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) + pure (getVerificationKey externalSigningKey, amount) + let fundingTransaction = + mkGenesisTx + networkId + faucetSk + (Coin availableInitialFunds) + clientFunds + pure fundingTransaction + +getFaucetInitialFunds :: VerificationKey PaymentKey -> SocketPath -> IO (TxIn, Coin) +getFaucetInitialFunds faucetVk nodeSocket = do + utxo <- queryUTxOFor networkId nodeSocket QueryTip faucetVk + let (initialInput, TxOut{txOutValue}) = Prelude.head (UTxO.pairs utxo) + let initialOutputValue = selectLovelace txOutValue + pure (initialInput, initialOutputValue) + +genDatasetConstantUTxODemo :: + -- | The faucet signing key + SigningKey PaymentKey -> + -- | Clients + [ClientKeys] -> + -- | Number of transactions + Int -> + -- | Funds available in faucet + (TxIn, Coin) -> + Gen Dataset +genDatasetConstantUTxODemo faucetSk allClientKeys nTxs (initialInput, coins@(Coin fundsAvailable)) = do + let nClients = length allClientKeys + -- Prepare funding transaction which will give every client's + -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get + -- funded in the beginning of the benchmark run. + clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do + amount <- Coin <$> choose (1, fundsAvailable `div` fromIntegral nClients) + pure (getVerificationKey externalSigningKey, amount) + let fundingTransaction = + mkInitialTx + networkId + faucetSk + coins + clientFunds + initialInput + genDatasetConstantUTxO allClientKeys nTxs fundingTransaction diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index 931eff31a88..205ee0494d9 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -14,6 +14,7 @@ import Hydra.Generator ( ClientDataset (..), Dataset (..), genDatasetConstantUTxO, + makeGenesisFundingTx, ) import Hydra.Ledger (ChainSlot (ChainSlot), applyTransactions) import Hydra.Ledger.Cardano (Tx, cardanoLedger) @@ -45,14 +46,16 @@ prop_keepsUTxOConstant = let ledgerEnv = newLedgerEnv defaultPParams -- XXX: non-exhaustive pattern match pure $ - forAll (genDatasetConstantUTxO faucetSk clientKeys n) $ - \Dataset{fundingTransaction, clientDatasets = [ClientDataset{txSequence}]} -> - let initialUTxO = utxoFromTx fundingTransaction - finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence - in length finalUTxO == length initialUTxO - & counterexample ("transactions: " <> prettyJSONString txSequence) - & counterexample ("utxo: " <> prettyJSONString initialUTxO) - & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) + forAll (makeGenesisFundingTx faucetSk clientKeys) $ \fundingTransaction -> do + dataset <- genDatasetConstantUTxO clientKeys n fundingTransaction + let Dataset{clientDatasets = [ClientDataset{txSequence}]} = dataset + initialUTxO = utxoFromTx fundingTransaction + finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence + pure $ + length finalUTxO == length initialUTxO + & counterexample ("transactions: " <> prettyJSONString txSequence) + & counterexample ("utxo: " <> prettyJSONString initialUTxO) + & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) apply :: Globals -> LedgerEnv LedgerEra -> UTxO -> Tx -> UTxO apply globals ledgerEnv utxo tx = From 1a16408f8052c908e45ed8e8cc7be026ed638b21 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 8 Aug 2024 16:28:14 +0200 Subject: [PATCH 008/115] Fix tx building for funding transaction This solves the problem of: SubmitTxValidationError (TxValidationErrorInCardanoMode (ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError (UtxowFailure (UtxoFailure (AlonzoInBabbageUtxoPredFailure (FeeTooSmallUTxO (Coin 169153) (Coin 0)))) :| [])))) The problem was we were using a build-raw transaction api instead of one which autobalances it. --- hydra-cluster/bench/Main.hs | 11 +++--- hydra-cluster/src/Hydra/Generator.hs | 52 +++++++++++++++------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index fe4ee8f687e..43603deb427 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -11,7 +11,7 @@ import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Util (keysFor) -import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxODemo, generateConstantUTxODataset, getFaucetInitialFunds) +import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxODemo, generateConstantUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) @@ -38,7 +38,6 @@ main = run outputDirectory datasetFiles action DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket} -> do workDir <- createSystemTempDirectory "demo-bench" - (faucetVk, faucetSk) <- keysFor Faucet clientKeys <- do aliceSk <- snd <$> keysFor Alice aliceFundsSk <- snd <$> keysFor AliceFunds @@ -50,13 +49,13 @@ main = bob = ClientKeys bobSk bobFundsSk carol = ClientKeys carolSk carolFundsSk pure [alice, bob, carol] - playDemo outputDirectory timeoutSeconds scalingFactor (faucetVk, faucetSk) clientKeys workDir networkId nodeSocket + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket where - playDemo outputDirectory timeoutSeconds scalingFactor (faucetVk, faucetSk) clientKeys workDir networkId nodeSocket = do + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket = do + (faucetVk, faucetSk) <- keysFor Faucet putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize - initialFunds <- getFaucetInitialFunds faucetVk nodeSocket - dataset <- generate $ genDatasetConstantUTxODemo faucetSk clientKeys numberOfTxs initialFunds + dataset <- genDatasetConstantUTxODemo (faucetVk, faucetSk) clientKeys numberOfTxs networkId nodeSocket -- TODO! remove as only needed for dbg let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index be70fe60c59..10bbb007851 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,16 +5,17 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (mkGenesisTx, mkInitialTx) +import CardanoClient (buildTransaction, mkGenesisTx, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) import Hydra.Chain.CardanoClient (QueryPoint (..), queryUTxOFor) +import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (Faucet), availableInitialFunds) import Hydra.Cluster.Util (keysFor) +import Hydra.Ledger (balance) import Hydra.Ledger.Cardano (genSigningKey, generateOneTransfer) import Test.QuickCheck (choose, generate, sized) -import Prelude qualified networkId :: NetworkId networkId = Testnet $ NetworkMagic 42 @@ -144,36 +145,39 @@ makeGenesisFundingTx faucetSk clientKeys = do clientFunds pure fundingTransaction -getFaucetInitialFunds :: VerificationKey PaymentKey -> SocketPath -> IO (TxIn, Coin) -getFaucetInitialFunds faucetVk nodeSocket = do - utxo <- queryUTxOFor networkId nodeSocket QueryTip faucetVk - let (initialInput, TxOut{txOutValue}) = Prelude.head (UTxO.pairs utxo) - let initialOutputValue = selectLovelace txOutValue - pure (initialInput, initialOutputValue) - genDatasetConstantUTxODemo :: - -- | The faucet signing key - SigningKey PaymentKey -> + -- | The faucet keys + (VerificationKey PaymentKey, SigningKey PaymentKey) -> -- | Clients [ClientKeys] -> -- | Number of transactions Int -> - -- | Funds available in faucet - (TxIn, Coin) -> - Gen Dataset -genDatasetConstantUTxODemo faucetSk allClientKeys nTxs (initialInput, coins@(Coin fundsAvailable)) = do + NetworkId -> + SocketPath -> + IO Dataset +genDatasetConstantUTxODemo (faucetVk, faucetSk) allClientKeys nTxs networkId' nodeSocket = do let nClients = length allClientKeys + faucetUTxO <- queryUTxOFor networkId nodeSocket QueryTip faucetVk + let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the benchmark run. clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do - amount <- Coin <$> choose (1, fundsAvailable `div` fromIntegral nClients) + amount <- Coin <$> generate (choose (1, fundsAvailable `div` fromIntegral nClients)) pure (getVerificationKey externalSigningKey, amount) - let fundingTransaction = - mkInitialTx - networkId - faucetSk - coins - clientFunds - initialInput - genDatasetConstantUTxO allClientKeys nTxs fundingTransaction + + let recipientOutputs = + flip map clientFunds $ \(vk, ll) -> + TxOut + (mkVkAddress networkId' vk) + (lovelaceToValue ll) + TxOutDatumNone + ReferenceScriptNone + let changeAddress = mkVkAddress networkId' faucetVk + fundingTransaction <- + buildTransaction networkId' nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case + Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} + Right body -> do + let signedTx = sign faucetSk body + pure signedTx + generate $ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction From 03da04b4c423d4ddfa81dda9f1a7b41f89dd2051 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 8 Aug 2024 19:13:48 +0200 Subject: [PATCH 009/115] Add hydra signing keys as option args to bench-demo --- hydra-cluster/bench/Bench/EndToEnd.hs | 7 +++---- hydra-cluster/bench/Bench/Options.hs | 17 +++++++++++++++++ hydra-cluster/bench/Main.hs | 14 ++++++++------ hydra-cluster/src/Hydra/Generator.hs | 9 +++------ hydra-cluster/test/Test/GeneratorSpec.hs | 15 +++++++-------- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 7d9bd2ac0b1..2a020a5a9e0 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -36,7 +36,7 @@ import Hydra.Cluster.Scenarios ( headIsInitializingWith, ) import Hydra.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) -import Hydra.Crypto (generateSigningKey) +import Hydra.Crypto (HydraKey, SigningKey, generateSigningKey) import Hydra.Generator (ClientDataset (..), ClientKeys (..), Dataset (..)) import Hydra.Ledger (txId) import Hydra.Logging (Tracer, withTracerOutputTo) @@ -102,10 +102,11 @@ benchDemo :: NetworkId -> SocketPath -> NominalDiffTime -> + [SigningKey HydraKey] -> FilePath -> Dataset -> IO Summary -benchDemo networkId nodeSocket timeoutSeconds workDir dataset@Dataset{clientDatasets, fundingTransaction} = do +benchDemo networkId nodeSocket timeoutSeconds hydraKeys workDir dataset@Dataset{clientDatasets, fundingTransaction} = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> @@ -120,8 +121,6 @@ benchDemo networkId nodeSocket timeoutSeconds workDir dataset@Dataset{clientData forM_ clientDatasets (fuelWith100Ada (contramap FromFaucet tracer) node) putStrLn $ "Connecting to hydra cluster in " <> workDir let hydraTracer = contramap FromHydraNode tracer - let cardanoKeys = map (\ClientDataset{clientKeys = ClientKeys{signingKey}} -> (getVerificationKey signingKey, signingKey)) clientDatasets - let hydraKeys = generateSigningKey . show <$> [1 .. toInteger (length cardanoKeys)] let parties = Set.fromList (deriveParty <$> hydraKeys) withConnectionToNode hydraTracer 1 $ \leader -> withConnectionToNode hydraTracer 2 $ \node2 -> diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index d542739f9e5..60dde95daa7 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -3,6 +3,7 @@ module Bench.Options where import Hydra.Prelude import Hydra.Cardano.Api (NetworkId, SocketPath) +import Hydra.Chain (maximumNumberOfParties) import Hydra.Options (networkIdParser, nodeSocketParser) import Options.Applicative ( Parser, @@ -49,6 +50,7 @@ data Options , timeoutSeconds :: NominalDiffTime , networkId :: NetworkId , nodeSocket :: SocketPath + , hydraSigningKeys :: [FilePath] } benchOptionsParser :: ParserInfo Options @@ -182,6 +184,21 @@ demoOptionsParser = <*> timeoutParser <*> networkIdParser <*> nodeSocketParser + <*> many hydraSigningKeyFileParser + +hydraSigningKeyFileParser :: Parser FilePath +hydraSigningKeyFileParser = + option + str + ( long "hydra-sk" + <> metavar "FILE" + <> help + ( "Hydra signing key of a party in the Head. Can be \ + \provided multiple times, once for each participant (current maximum limit is " + <> show maximumNumberOfParties + <> " )." + ) + ) datasetOptionsInfo :: ParserInfo Options datasetOptionsInfo = diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 43603deb427..dc71a4a9f5e 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -9,8 +9,11 @@ import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) +import Hydra.Cardano.Api (AsType (..)) +import Hydra.Chain.Direct.Util (readFileTextEnvelopeThrow) import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Util (keysFor) +import Hydra.Crypto (AsType (..)) import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxODemo, generateConstantUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) @@ -36,7 +39,7 @@ main = DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do let action = bench startingNodeId timeoutSeconds run outputDirectory datasetFiles action - DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket} -> do + DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket, hydraSigningKeys} -> do workDir <- createSystemTempDirectory "demo-bench" clientKeys <- do aliceSk <- snd <$> keysFor Alice @@ -49,18 +52,17 @@ main = bob = ClientKeys bobSk bobFundsSk carol = ClientKeys carolSk carolFundsSk pure [alice, bob, carol] - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket + hydraKeys <- mapM (readFileTextEnvelopeThrow (AsSigningKey AsHydraKey)) hydraSigningKeys + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket hydraKeys where - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket = do + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket hydraKeys = do (faucetVk, faucetSk) <- keysFor Faucet putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize dataset <- genDatasetConstantUTxODemo (faucetVk, faucetSk) clientKeys numberOfTxs networkId nodeSocket - -- TODO! remove as only needed for dbg let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset - -- - let action = benchDemo networkId nodeSocket timeoutSeconds + let action = benchDemo networkId nodeSocket timeoutSeconds hydraKeys run outputDirectory [datasetPath] action play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 10bbb007851..50f1722cbe2 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -105,9 +105,6 @@ genDatasetConstantUTxO :: Tx -> Gen Dataset genDatasetConstantUTxO allClientKeys nTxs fundingTransaction = do - -- Prepare funding transaction which will give every client's - -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get - -- funded in the beginning of the benchmark run. clientDatasets <- forM allClientKeys generateClientDataset pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where @@ -128,12 +125,12 @@ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction = do thrd (_, _, c) = c +-- Prepare funding transaction which will give every client's +-- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get +-- funded in the beginning of the benchmark run. makeGenesisFundingTx :: SigningKey PaymentKey -> [ClientKeys] -> Gen Tx makeGenesisFundingTx faucetSk clientKeys = do let nClients = length clientKeys - -- Prepare funding transaction which will give every client's - -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get - -- funded in the beginning of the benchmark run. clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) pure (getVerificationKey externalSigningKey, amount) diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index 205ee0494d9..63487f5608c 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -24,13 +24,7 @@ import Hydra.Ledger.Cardano.Configuration ( newLedgerEnv, ) import Test.Aeson.GenericSpecs (roundtripSpecs) -import Test.QuickCheck ( - Positive (Positive), - Property, - counterexample, - forAll, - idempotentIOProperty, - ) +import Test.QuickCheck (Positive (Positive), Property, counterexample, forAll, idempotentIOProperty) spec :: Spec spec = parallel $ do @@ -39,11 +33,16 @@ spec = parallel $ do prop_keepsUTxOConstant :: Property prop_keepsUTxOConstant = - forAll arbitrary $ \(Positive n, clientKeys) -> do + forAll arbitrary $ \(Positive n) -> do idempotentIOProperty $ do faucetSk <- snd <$> keysFor Faucet let ledgerEnv = newLedgerEnv defaultPParams + + let clientKey = generateWith arbitrary 42 + -- REVIEW: should not we generate a dataset given multiple keys? + let clientKeys = [clientKey] + -- XXX: non-exhaustive pattern match pure $ forAll (makeGenesisFundingTx faucetSk clientKeys) $ \fundingTransaction -> do From 47f105c593d6f3188410a2d04429d0640df81a4b Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 12 Aug 2024 14:09:11 +0200 Subject: [PATCH 010/115] Extend generator of constant utxo property To be valid for an arbitrary set of client keys, instead of just a single one. --- hydra-cluster/test/Test/GeneratorSpec.hs | 27 +++++++++++------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index 63487f5608c..e2814533409 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -24,7 +24,7 @@ import Hydra.Ledger.Cardano.Configuration ( newLedgerEnv, ) import Test.Aeson.GenericSpecs (roundtripSpecs) -import Test.QuickCheck (Positive (Positive), Property, counterexample, forAll, idempotentIOProperty) +import Test.QuickCheck (NonEmptyList (NonEmpty), Positive (Positive), Property, conjoin, counterexample, forAll, idempotentIOProperty) spec :: Spec spec = parallel $ do @@ -33,28 +33,25 @@ spec = parallel $ do prop_keepsUTxOConstant :: Property prop_keepsUTxOConstant = - forAll arbitrary $ \(Positive n) -> do + forAll arbitrary $ \(Positive n, NonEmpty clientKeys) -> do idempotentIOProperty $ do faucetSk <- snd <$> keysFor Faucet let ledgerEnv = newLedgerEnv defaultPParams - let clientKey = generateWith arbitrary 42 - -- REVIEW: should not we generate a dataset given multiple keys? - let clientKeys = [clientKey] - -- XXX: non-exhaustive pattern match pure $ forAll (makeGenesisFundingTx faucetSk clientKeys) $ \fundingTransaction -> do - dataset <- genDatasetConstantUTxO clientKeys n fundingTransaction - let Dataset{clientDatasets = [ClientDataset{txSequence}]} = dataset - initialUTxO = utxoFromTx fundingTransaction - finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence - pure $ - length finalUTxO == length initialUTxO - & counterexample ("transactions: " <> prettyJSONString txSequence) - & counterexample ("utxo: " <> prettyJSONString initialUTxO) - & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) + Dataset{clientDatasets} <- genDatasetConstantUTxO clientKeys n fundingTransaction + allProperties <- forM clientDatasets $ \ClientDataset{txSequence} -> do + let initialUTxO = utxoFromTx fundingTransaction + finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence + pure $ + length finalUTxO == length initialUTxO + & counterexample ("transactions: " <> prettyJSONString txSequence) + & counterexample ("utxo: " <> prettyJSONString initialUTxO) + & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) + pure $ conjoin allProperties apply :: Globals -> LedgerEnv LedgerEra -> UTxO -> Tx -> UTxO apply globals ledgerEnv utxo tx = From 6b5b52e69bb6d83eab9ebebe3ab5ef316b357e61 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Mon, 12 Aug 2024 15:43:17 +0100 Subject: [PATCH 011/115] Trying to work out why large numbers of clients fail - Also note strange keys generated when allocating only small amounts of lovelace --- hydra-cluster/src/Hydra/Generator.hs | 2 ++ hydra-cluster/test/Test/GeneratorSpec.hs | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 50f1722cbe2..14789f1ac41 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -133,6 +133,8 @@ makeGenesisFundingTx faucetSk clientKeys = do let nClients = length clientKeys clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) + -- NOTE: It seems if this becomes choose (1,1) we get funny signingKeys; + -- i.e. like "0001010100010001000000010100000001010001000101000000010101010001". pure (getVerificationKey externalSigningKey, amount) let fundingTransaction = mkGenesisTx diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index e2814533409..1dec4a5a9c7 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -7,6 +7,7 @@ import Test.Hydra.Prelude import Data.Text (unpack) import Hydra.Cardano.Api (LedgerEra, UTxO, prettyPrintJSON, utxoFromTx) +import Hydra.Chain (maximumNumberOfParties) import Hydra.Chain.Direct.Fixture (defaultGlobals, defaultPParams) import Hydra.Cluster.Fixture (Actor (Faucet)) import Hydra.Cluster.Util (keysFor) @@ -24,7 +25,7 @@ import Hydra.Ledger.Cardano.Configuration ( newLedgerEnv, ) import Test.Aeson.GenericSpecs (roundtripSpecs) -import Test.QuickCheck (NonEmptyList (NonEmpty), Positive (Positive), Property, conjoin, counterexample, forAll, idempotentIOProperty) +import Test.QuickCheck (Positive (Positive), Property, conjoin, counterexample, forAll, idempotentIOProperty) spec :: Spec spec = parallel $ do @@ -33,7 +34,8 @@ spec = parallel $ do prop_keepsUTxOConstant :: Property prop_keepsUTxOConstant = - forAll arbitrary $ \(Positive n, NonEmpty clientKeys) -> do + -- FIXME: Breaks with values > 33. Why? + forAll (inputs maximumNumberOfParties) $ \(Positive n, clientKeys) -> do idempotentIOProperty $ do faucetSk <- snd <$> keysFor Faucet @@ -52,6 +54,11 @@ prop_keepsUTxOConstant = & counterexample ("utxo: " <> prettyJSONString initialUTxO) & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) pure $ conjoin allProperties + where + inputs nClients = do + n <- arbitrary + clientKeys <- replicateM nClients arbitrary + pure (n, clientKeys) apply :: Globals -> LedgerEnv LedgerEra -> UTxO -> Tx -> UTxO apply globals ledgerEnv utxo tx = From 85c6f75f1bb767d51045917e9eb121c3ba3c5bad Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 12 Aug 2024 20:02:57 +0200 Subject: [PATCH 012/115] Limit the nbr of client keys on arbitrary for dataset --- hydra-cluster/src/Hydra/Generator.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 14789f1ac41..99ed9b79324 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -9,13 +9,14 @@ import CardanoClient (buildTransaction, mkGenesisTx, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) +import Hydra.Chain (maximumNumberOfParties) import Hydra.Chain.CardanoClient (QueryPoint (..), queryUTxOFor) import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (Faucet), availableInitialFunds) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) import Hydra.Ledger.Cardano (genSigningKey, generateOneTransfer) -import Test.QuickCheck (choose, generate, sized) +import Test.QuickCheck (choose, generate, sized, vectorOf) networkId :: NetworkId networkId = Testnet $ NetworkMagic 42 @@ -36,8 +37,8 @@ data Dataset = Dataset instance Arbitrary Dataset where arbitrary = sized $ \n -> do faucetSk <- genSigningKey - let nClients = n `div` 10 - let clientKeys = replicate nClients (generateWith arbitrary 42) + let nClients = max 1 (min maximumNumberOfParties (n `div` 10)) + clientKeys <- vectorOf nClients arbitrary fundingTransaction <- makeGenesisFundingTx faucetSk clientKeys genDatasetConstantUTxO clientKeys n fundingTransaction From f338f27fe2ae91b579102a380d18577acb521168 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 13 Aug 2024 11:56:45 +0200 Subject: [PATCH 013/115] Return funds to faucet when it finishes --- hydra-cluster/bench/Bench/EndToEnd.hs | 56 +++++++++++++++-------- hydra-cluster/bench/Main.hs | 18 +++----- hydra-cluster/src/Hydra/Cluster/Faucet.hs | 31 +++++++++---- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 2a020a5a9e0..af0a64544ff 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -28,9 +28,9 @@ import Data.Scientific (Scientific) import Data.Set ((\\)) import Data.Set qualified as Set import Data.Time (UTCTime (UTCTime), utctDayTime) -import Hydra.Cardano.Api (NetworkId, SocketPath, Tx, TxId, UTxO, getVerificationKey, signTx) -import Hydra.Cluster.Faucet (FaucetLog, publishHydraScriptsAs, seedFromFaucet) -import Hydra.Cluster.Fixture (Actor (Faucet)) +import Hydra.Cardano.Api (NetworkId, PaymentKey, SocketPath, Tx, TxId, UTxO, VerificationKey, getVerificationKey, signTx) +import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, returnFundsToFaucet', seedFromFaucet) +import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Scenarios ( EndToEndLog (..), headIsInitializingWith, @@ -39,7 +39,7 @@ import Hydra.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) import Hydra.Crypto (HydraKey, SigningKey, generateSigningKey) import Hydra.Generator (ClientDataset (..), ClientKeys (..), Dataset (..)) import Hydra.Ledger (txId) -import Hydra.Logging (Tracer, withTracerOutputTo) +import Hydra.Logging (Tracer, traceWith, withTracerOutputTo) import Hydra.Party (Party, deriveParty) import HydraNode ( HydraClient, @@ -102,31 +102,47 @@ benchDemo :: NetworkId -> SocketPath -> NominalDiffTime -> + VerificationKey PaymentKey -> [SigningKey HydraKey] -> FilePath -> Dataset -> IO Summary -benchDemo networkId nodeSocket timeoutSeconds hydraKeys workDir dataset@Dataset{clientDatasets, fundingTransaction} = do +benchDemo networkId nodeSocket timeoutSeconds faucetVk hydraKeys workDir dataset@Dataset{clientDatasets, fundingTransaction} = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> failAfter timeoutSeconds $ do putTextLn "Starting benchmark" - findRunningCardanoNode' (contramap FromCardanoNode tracer) networkId nodeSocket >>= \case + let cardanoTracer = contramap FromCardanoNode tracer + findRunningCardanoNode' cardanoTracer networkId nodeSocket >>= \case Nothing -> error ("Not found running node at socket: " <> show nodeSocket <> ", and network: " <> show networkId) Just node -> do - putTextLn "Seeding network" - fundClients networkId nodeSocket fundingTransaction - forM_ clientDatasets (fuelWith100Ada (contramap FromFaucet tracer) node) - putStrLn $ "Connecting to hydra cluster in " <> workDir - let hydraTracer = contramap FromHydraNode tracer - let parties = Set.fromList (deriveParty <$> hydraKeys) - withConnectionToNode hydraTracer 1 $ \leader -> - withConnectionToNode hydraTracer 2 $ \node2 -> - withConnectionToNode hydraTracer 3 $ \node3 -> do - let followers = [node2, node3] - scenario hydraTracer node workDir dataset parties leader followers + let clientSks = clientKeys <$> clientDatasets + (`finally` returnFaucetFunds tracer node clientSks) $ do + putTextLn "Seeding network" + fundClients networkId nodeSocket fundingTransaction + forM_ clientSks (fuelWith100Ada (contramap FromFaucet tracer) node) + putStrLn $ "Connecting to hydra cluster in " <> workDir + let hydraTracer = contramap FromHydraNode tracer + let parties = Set.fromList (deriveParty <$> hydraKeys) + withConnectionToNode hydraTracer 1 $ \leader -> + withConnectionToNode hydraTracer 2 $ \node2 -> + withConnectionToNode hydraTracer 3 $ \node3 -> do + let followers = [node2, node3] + scenario hydraTracer node workDir dataset parties leader followers + where + returnFaucetFunds tracer node cKeys = do + putTextLn "Returning funds to faucet" + let faucetTracer = contramap FromFaucet tracer + let toSenders (ClientKeys sk esk) = [(getVerificationKey sk, sk), (getVerificationKey esk, esk)] + let senders = concatMap @[] toSenders cKeys + mapM_ + ( \sender -> do + returnAmount <- returnFundsToFaucet' faucetTracer node faucetVk sender + traceWith faucetTracer $ ReturnedFunds{actor = show sender, returnAmount} + ) + senders scenario :: Tracer IO HydraNodeLog -> @@ -283,7 +299,7 @@ movingAverage confirmations = seedNetwork :: RunningNode -> Dataset -> Tracer IO FaucetLog -> IO TxId seedNetwork node@RunningNode{nodeSocket, networkId} Dataset{fundingTransaction, clientDatasets} tracer = do fundClients networkId nodeSocket fundingTransaction - forM_ clientDatasets (fuelWith100Ada tracer node) + forM_ (clientKeys <$> clientDatasets) (fuelWith100Ada tracer node) putTextLn "Publishing hydra scripts" publishHydraScriptsAs node Faucet @@ -293,8 +309,8 @@ fundClients networkId nodeSocket fundingTransaction = do submitTransaction networkId nodeSocket fundingTransaction void $ awaitTransaction networkId nodeSocket fundingTransaction -fuelWith100Ada :: Tracer IO FaucetLog -> RunningNode -> ClientDataset -> IO UTxO -fuelWith100Ada tracer node ClientDataset{clientKeys = ClientKeys{signingKey}} = do +fuelWith100Ada :: Tracer IO FaucetLog -> RunningNode -> ClientKeys -> IO UTxO +fuelWith100Ada tracer node ClientKeys{signingKey} = do let vk = getVerificationKey signingKey putTextLn $ "Seed client " <> show vk seedFromFaucet node vk 100_000_000 tracer diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index dc71a4a9f5e..448a24b2202 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -42,16 +42,12 @@ main = DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket, hydraSigningKeys} -> do workDir <- createSystemTempDirectory "demo-bench" clientKeys <- do - aliceSk <- snd <$> keysFor Alice - aliceFundsSk <- snd <$> keysFor AliceFunds - bobSk <- snd <$> keysFor Bob - bobFundsSk <- snd <$> keysFor BobFunds - carolSk <- snd <$> keysFor Carol - carolFundsSk <- snd <$> keysFor CarolFunds - let alice = ClientKeys aliceSk aliceFundsSk - bob = ClientKeys bobSk bobFundsSk - carol = ClientKeys carolSk carolFundsSk - pure [alice, bob, carol] + let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] + let toClientKeys (actor, actorFunds) = do + sk <- snd <$> keysFor actor + fundsSk <- snd <$> keysFor actorFunds + pure $ ClientKeys sk fundsSk + forM actors toClientKeys hydraKeys <- mapM (readFileTextEnvelopeThrow (AsSigningKey AsHydraKey)) hydraSigningKeys playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket hydraKeys where @@ -62,7 +58,7 @@ main = dataset <- genDatasetConstantUTxODemo (faucetVk, faucetSk) clientKeys numberOfTxs networkId nodeSocket let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset - let action = benchDemo networkId nodeSocket timeoutSeconds hydraKeys + let action = benchDemo networkId nodeSocket timeoutSeconds faucetVk hydraKeys run outputDirectory [datasetPath] action play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do diff --git a/hydra-cluster/src/Hydra/Cluster/Faucet.hs b/hydra-cluster/src/Hydra/Cluster/Faucet.hs index 4d62dee0e4e..cc4b10ebd8a 100644 --- a/hydra-cluster/src/Hydra/Cluster/Faucet.hs +++ b/hydra-cluster/src/Hydra/Cluster/Faucet.hs @@ -106,19 +106,30 @@ returnFundsToFaucet :: RunningNode -> Actor -> IO () -returnFundsToFaucet tracer RunningNode{networkId, nodeSocket} sender = do +returnFundsToFaucet tracer node sender = do (faucetVk, _) <- keysFor Faucet - let faucetAddress = mkVkAddress networkId faucetVk + senderKeys <- keysFor sender + returnAmount <- returnFundsToFaucet' tracer node faucetVk senderKeys + traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount} - (senderVk, senderSk) <- keysFor sender +returnFundsToFaucet' :: + Tracer IO FaucetLog -> + RunningNode -> + VerificationKey PaymentKey -> + (VerificationKey PaymentKey, SigningKey PaymentKey) -> + IO Coin +returnFundsToFaucet' tracer RunningNode{networkId, nodeSocket} faucetVk (senderVk, senderSk) = do + let faucetAddress = mkVkAddress networkId faucetVk utxo <- queryUTxOFor networkId nodeSocket QueryTip senderVk - unless (null utxo) . retryOnExceptions tracer $ do - let utxoValue = balance @Tx utxo - let allLovelace = selectLovelace utxoValue - tx <- sign senderSk <$> buildTxBody utxo faucetAddress - submitTransaction networkId nodeSocket tx - void $ awaitTransaction networkId nodeSocket tx - traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount = allLovelace} + if null utxo + then pure 0 + else retryOnExceptions tracer $ do + let utxoValue = balance @Tx utxo + let allLovelace = selectLovelace utxoValue + tx <- sign senderSk <$> buildTxBody utxo faucetAddress + submitTransaction networkId nodeSocket tx + void $ awaitTransaction networkId nodeSocket tx + pure allLovelace where buildTxBody utxo faucetAddress = -- Here we specify no outputs in the transaction so that a change output with the From 8cb5813b655117aaba4213e30fc1a055fc51bdf6 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 13 Aug 2024 18:05:11 +0200 Subject: [PATCH 014/115] Avoid passing network id to options and faucet keys on generators As we forcing the bench to run on devnet for the moment. --- hydra-cluster/bench/Bench/EndToEnd.hs | 10 ++++---- hydra-cluster/bench/Bench/Options.hs | 11 +++++---- hydra-cluster/bench/Main.hs | 13 +++++------ hydra-cluster/src/CardanoClient.hs | 28 +++++------------------ hydra-cluster/src/CardanoNode.hs | 2 ++ hydra-cluster/src/Hydra/Cluster/Faucet.hs | 10 ++++---- hydra-cluster/src/Hydra/Generator.hs | 15 ++++++++---- 7 files changed, 39 insertions(+), 50 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index af0a64544ff..aef547b938f 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -28,7 +28,7 @@ import Data.Scientific (Scientific) import Data.Set ((\\)) import Data.Set qualified as Set import Data.Time (UTCTime (UTCTime), utctDayTime) -import Hydra.Cardano.Api (NetworkId, PaymentKey, SocketPath, Tx, TxId, UTxO, VerificationKey, getVerificationKey, signTx) +import Hydra.Cardano.Api (NetworkId, SocketPath, Tx, TxId, UTxO, getVerificationKey, signTx) import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, returnFundsToFaucet', seedFromFaucet) import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Scenarios ( @@ -102,12 +102,11 @@ benchDemo :: NetworkId -> SocketPath -> NominalDiffTime -> - VerificationKey PaymentKey -> [SigningKey HydraKey] -> FilePath -> Dataset -> IO Summary -benchDemo networkId nodeSocket timeoutSeconds faucetVk hydraKeys workDir dataset@Dataset{clientDatasets, fundingTransaction} = do +benchDemo networkId nodeSocket timeoutSeconds hydraKeys workDir dataset@Dataset{clientDatasets, fundingTransaction} = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> @@ -135,11 +134,10 @@ benchDemo networkId nodeSocket timeoutSeconds faucetVk hydraKeys workDir dataset returnFaucetFunds tracer node cKeys = do putTextLn "Returning funds to faucet" let faucetTracer = contramap FromFaucet tracer - let toSenders (ClientKeys sk esk) = [(getVerificationKey sk, sk), (getVerificationKey esk, esk)] - let senders = concatMap @[] toSenders cKeys + let senders = concatMap @[] (\(ClientKeys sk esk) -> [sk, esk]) cKeys mapM_ ( \sender -> do - returnAmount <- returnFundsToFaucet' faucetTracer node faucetVk sender + returnAmount <- returnFundsToFaucet' faucetTracer node sender traceWith faucetTracer $ ReturnedFunds{actor = show sender, returnAmount} ) senders diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index 60dde95daa7..da4859c373f 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -2,9 +2,9 @@ module Bench.Options where import Hydra.Prelude -import Hydra.Cardano.Api (NetworkId, SocketPath) +import Hydra.Cardano.Api (SocketPath) import Hydra.Chain (maximumNumberOfParties) -import Hydra.Options (networkIdParser, nodeSocketParser) +import Hydra.Options (nodeSocketParser) import Options.Applicative ( Parser, ParserInfo, @@ -48,7 +48,6 @@ data Options { outputDirectory :: Maybe FilePath , scalingFactor :: Int , timeoutSeconds :: NominalDiffTime - , networkId :: NetworkId , nodeSocket :: SocketPath , hydraSigningKeys :: [FilePath] } @@ -173,7 +172,10 @@ demoOptionsInfo = info demoOptionsParser ( progDesc - "Run scenarios from local running demo." + "Run bench scenario over local demo. \ + \ This requires having in the background: \ + \ * cardano node running on specified node-socket. \ + \ * three hydra nodes listening on ports 4001, 4002 and 4003." ) demoOptionsParser :: Parser Options @@ -182,7 +184,6 @@ demoOptionsParser = <$> optional outputDirectoryParser <*> scalingFactorParser <*> timeoutParser - <*> networkIdParser <*> nodeSocketParser <*> many hydraSigningKeyFileParser diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 448a24b2202..3d761ee30e3 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -11,7 +11,7 @@ import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) import Hydra.Cardano.Api (AsType (..)) import Hydra.Chain.Direct.Util (readFileTextEnvelopeThrow) -import Hydra.Cluster.Fixture (Actor (..)) +import Hydra.Cluster.Fixture (Actor (..), defaultNetworkId) import Hydra.Cluster.Util (keysFor) import Hydra.Crypto (AsType (..)) import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxODemo, generateConstantUTxODataset) @@ -39,7 +39,7 @@ main = DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do let action = bench startingNodeId timeoutSeconds run outputDirectory datasetFiles action - DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket, hydraSigningKeys} -> do + DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, nodeSocket, hydraSigningKeys} -> do workDir <- createSystemTempDirectory "demo-bench" clientKeys <- do let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] @@ -49,16 +49,15 @@ main = pure $ ClientKeys sk fundsSk forM actors toClientKeys hydraKeys <- mapM (readFileTextEnvelopeThrow (AsSigningKey AsHydraKey)) hydraSigningKeys - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket hydraKeys + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraKeys where - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir networkId nodeSocket hydraKeys = do - (faucetVk, faucetSk) <- keysFor Faucet + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraKeys = do putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize - dataset <- genDatasetConstantUTxODemo (faucetVk, faucetSk) clientKeys numberOfTxs networkId nodeSocket + dataset <- genDatasetConstantUTxODemo clientKeys numberOfTxs defaultNetworkId nodeSocket let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset - let action = benchDemo networkId nodeSocket timeoutSeconds faucetVk hydraKeys + let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraKeys run outputDirectory [datasetPath] action play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do diff --git a/hydra-cluster/src/CardanoClient.hs b/hydra-cluster/src/CardanoClient.hs index cd52a0d3dc9..c40a978507b 100644 --- a/hydra-cluster/src/CardanoClient.hs +++ b/hydra-cluster/src/CardanoClient.hs @@ -111,20 +111,21 @@ waitForUTxO node utxo = txOut -> error $ "Unexpected TxOut " <> show txOut -mkInitialTx :: +-- | Helper used to generate transaction datasets for use in hydra-cluster benchmarks. +buildRawTransaction :: NetworkId -> + -- | Initial input from which to spend + TxIn -> -- | Owner of the 'initialFund'. SigningKey PaymentKey -> -- | Amount of initialFunds Coin -> -- | Recipients and amounts to pay in this transaction. [(VerificationKey PaymentKey, Coin)] -> - TxIn -> - -- | Initial input from which to spend Tx -mkInitialTx networkId signingKey initialAmount recipients initialInput = +buildRawTransaction networkId initialInput signingKey initialAmount recipients = case buildRaw [initialInput] (recipientOutputs <> [changeOutput]) of - Left err -> error $ "Fail to build genesis transations: " <> show err + Left err -> error $ "Fail to build raw transations: " <> show err Right tx -> sign signingKey tx where totalSent = foldMap snd recipients @@ -145,23 +146,6 @@ mkInitialTx networkId signingKey initialAmount recipients initialInput = TxOutDatumNone ReferenceScriptNone -mkGenesisTx :: - NetworkId -> - -- | Owner of the 'initialFund'. - SigningKey PaymentKey -> - -- | Amount of initialFunds - Coin -> - -- | Recipients and amounts to pay in this transaction. - [(VerificationKey PaymentKey, Coin)] -> - Tx -mkGenesisTx networkId signingKey initialAmount recipients = - mkInitialTx networkId signingKey initialAmount recipients initialInput - where - initialInput = - genesisUTxOPseudoTxIn - networkId - (unsafeCastHash $ verificationKeyHash $ getVerificationKey signingKey) - data RunningNode = RunningNode { nodeSocket :: SocketPath , networkId :: NetworkId diff --git a/hydra-cluster/src/CardanoNode.hs b/hydra-cluster/src/CardanoNode.hs index 2251ca7710f..e613103756a 100644 --- a/hydra-cluster/src/CardanoNode.hs +++ b/hydra-cluster/src/CardanoNode.hs @@ -140,6 +140,8 @@ findRunningCardanoNode tracer workDir knownNetwork = do CardanoNodeArgs{nodeSocket} = defaultCardanoNodeArgs +-- | Tries to find an communicate with an existing cardano-node running in given +-- network id and socket path. findRunningCardanoNode' :: Tracer IO NodeLog -> NetworkId -> SocketPath -> IO (Maybe RunningNode) findRunningCardanoNode' tracer networkId nodeSocket = do try (queryGenesisParameters networkId nodeSocket QueryTip) >>= \case diff --git a/hydra-cluster/src/Hydra/Cluster/Faucet.hs b/hydra-cluster/src/Hydra/Cluster/Faucet.hs index cc4b10ebd8a..59c20e4a96c 100644 --- a/hydra-cluster/src/Hydra/Cluster/Faucet.hs +++ b/hydra-cluster/src/Hydra/Cluster/Faucet.hs @@ -107,19 +107,19 @@ returnFundsToFaucet :: Actor -> IO () returnFundsToFaucet tracer node sender = do - (faucetVk, _) <- keysFor Faucet senderKeys <- keysFor sender - returnAmount <- returnFundsToFaucet' tracer node faucetVk senderKeys + returnAmount <- returnFundsToFaucet' tracer node (snd senderKeys) traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount} returnFundsToFaucet' :: Tracer IO FaucetLog -> RunningNode -> - VerificationKey PaymentKey -> - (VerificationKey PaymentKey, SigningKey PaymentKey) -> + SigningKey PaymentKey -> IO Coin -returnFundsToFaucet' tracer RunningNode{networkId, nodeSocket} faucetVk (senderVk, senderSk) = do +returnFundsToFaucet' tracer RunningNode{networkId, nodeSocket} senderSk = do + (faucetVk, _) <- keysFor Faucet let faucetAddress = mkVkAddress networkId faucetVk + let senderVk = getVerificationKey senderSk utxo <- queryUTxOFor networkId nodeSocket QueryTip senderVk if null utxo then pure 0 diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 99ed9b79324..99c65b4fe64 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,7 +5,7 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (buildTransaction, mkGenesisTx, sign) +import CardanoClient (buildRawTransaction, buildTransaction, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) @@ -138,16 +138,20 @@ makeGenesisFundingTx faucetSk clientKeys = do -- i.e. like "0001010100010001000000010100000001010001000101000000010101010001". pure (getVerificationKey externalSigningKey, amount) let fundingTransaction = - mkGenesisTx + buildRawTransaction networkId + initialInput faucetSk (Coin availableInitialFunds) clientFunds pure fundingTransaction + where + initialInput = + genesisUTxOPseudoTxIn + networkId + (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) genDatasetConstantUTxODemo :: - -- | The faucet keys - (VerificationKey PaymentKey, SigningKey PaymentKey) -> -- | Clients [ClientKeys] -> -- | Number of transactions @@ -155,7 +159,8 @@ genDatasetConstantUTxODemo :: NetworkId -> SocketPath -> IO Dataset -genDatasetConstantUTxODemo (faucetVk, faucetSk) allClientKeys nTxs networkId' nodeSocket = do +genDatasetConstantUTxODemo allClientKeys nTxs networkId' nodeSocket = do + (faucetVk, faucetSk) <- keysFor Faucet let nClients = length allClientKeys faucetUTxO <- queryUTxOFor networkId nodeSocket QueryTip faucetVk let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) From 74d689630e3bc1a3b38d6a1b07544e7b85a403d1 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 14 Aug 2024 09:56:53 +0200 Subject: [PATCH 015/115] Make bench-demo sim to run on top of previous run For that we made the following changes: - avoid requesting for node's history upon connection - datasets transactions use sender same as recipient - finally we avoid waiting for nodes to be connected given that is assumed in this case --- hydra-cluster/bench/Bench/EndToEnd.hs | 9 +++++---- hydra-cluster/src/HydraNode.hs | 9 +++++---- hydra-node/src/Hydra/Ledger/Cardano.hs | 3 +-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index aef547b938f..517fefb4b51 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -96,6 +96,8 @@ bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets} = do let contestationPeriod = UnsafeContestationPeriod 10 putStrLn $ "Starting hydra cluster in " <> workDir withHydraCluster hydraTracer workDir nodeSocket startingNodeId cardanoKeys hydraKeys hydraScriptsTxId contestationPeriod $ \(leader :| followers) -> do + let clients = leader : followers + waitForNodesConnected hydraTracer 20 clients scenario hydraTracer node workDir dataset parties leader followers benchDemo :: @@ -125,9 +127,9 @@ benchDemo networkId nodeSocket timeoutSeconds hydraKeys workDir dataset@Dataset{ putStrLn $ "Connecting to hydra cluster in " <> workDir let hydraTracer = contramap FromHydraNode tracer let parties = Set.fromList (deriveParty <$> hydraKeys) - withConnectionToNode hydraTracer 1 $ \leader -> - withConnectionToNode hydraTracer 2 $ \node2 -> - withConnectionToNode hydraTracer 3 $ \node3 -> do + withConnectionToNode hydraTracer 1 False $ \leader -> + withConnectionToNode hydraTracer 2 False $ \node2 -> + withConnectionToNode hydraTracer 3 False $ \node3 -> do let followers = [node2, node3] scenario hydraTracer node workDir dataset parties leader followers where @@ -154,7 +156,6 @@ scenario :: scenario hydraTracer node workDir dataset@Dataset{clientDatasets, title, description} parties leader followers = do let clusterSize = fromIntegral $ length clientDatasets let clients = leader : followers - waitForNodesConnected hydraTracer 20 clients putTextLn "Initializing Head" send leader $ input "Init" [] diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index cb79b183a6d..4f918148e0d 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -309,7 +309,7 @@ withHydraNode tracer chainConfig workDir hydraNodeId hydraSKey hydraVKeys allNod \_ err processHandle -> do race (checkProcessHasNotDied ("hydra-node (" <> show hydraNodeId <> ")") processHandle (Just err)) - (withConnectionToNode tracer hydraNodeId action) + (withConnectionToNode tracer hydraNodeId True action) <&> either absurd id where logFilePath = workDir "logs" "hydra-node-" <> show hydraNodeId <.> "log" @@ -401,8 +401,8 @@ withHydraNode' tracer chainConfig workDir hydraNodeId hydraSKey hydraVKeys allNo , i /= hydraNodeId ] -withConnectionToNode :: forall a. Tracer IO HydraNodeLog -> Int -> (HydraClient -> IO a) -> IO a -withConnectionToNode tracer hydraNodeId action = do +withConnectionToNode :: forall a. Tracer IO HydraNodeLog -> Int -> Bool -> (HydraClient -> IO a) -> IO a +withConnectionToNode tracer hydraNodeId showHistory action = do connectedOnce <- newIORef False tryConnect connectedOnce (200 :: Int) where @@ -420,7 +420,8 @@ withConnectionToNode tracer hydraNodeId action = do , Handler $ retryOrThrow (Proxy @HandshakeException) ] - doConnect connectedOnce = runClient "127.0.0.1" (4_000 + hydraNodeId) "/" $ \connection -> do + historyMode = if showHistory then "/" else "/?history=no" + doConnect connectedOnce = runClient "127.0.0.1" (4_000 + hydraNodeId) historyMode $ \connection -> do atomicWriteIORef connectedOnce True traceWith tracer (NodeStarted hydraNodeId) res <- action $ HydraClient{hydraNodeId, connection, tracer} diff --git a/hydra-node/src/Hydra/Ledger/Cardano.hs b/hydra-node/src/Hydra/Ledger/Cardano.hs index 4b6c36f7953..7214a0fc468 100644 --- a/hydra-node/src/Hydra/Ledger/Cardano.hs +++ b/hydra-node/src/Hydra/Ledger/Cardano.hs @@ -298,8 +298,7 @@ generateOneTransfer :: (UTxO, (VerificationKey PaymentKey, SigningKey PaymentKey), [Tx]) -> Int -> Gen (UTxO, (VerificationKey PaymentKey, SigningKey PaymentKey), [Tx]) -generateOneTransfer networkId (utxo, (_, sender), txs) _ = do - recipient <- genKeyPair +generateOneTransfer networkId (utxo, recipient@(_, sender), txs) _ = do -- NOTE(AB): elements is partial, it crashes if given an empty list, We don't expect -- this function to be ever used in production, and crash will be caught in tests case UTxO.pairs utxo of From b6d53d8e37f84522c6811126524b97baa4c636bf Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 14 Aug 2024 23:43:57 +0200 Subject: [PATCH 016/115] Replace hydra-keys options by hydra-client hosts instead Given we connect to provided hydra-clients we don't need to check we observe a head is inicializing with the same parties as its assumed. Instead we take hydra-client hosts so that the solution scales in other settings. --- hydra-cluster/bench/Bench/EndToEnd.hs | 31 ++++++++---- hydra-cluster/bench/Bench/Options.hs | 53 ++++++-------------- hydra-cluster/bench/Main.hs | 12 ++--- hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 8 +++ hydra-cluster/src/HydraNode.hs | 12 ++++- 5 files changed, 58 insertions(+), 58 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 517fefb4b51..fa470654376 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -33,13 +33,15 @@ import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, returnFundsT import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Scenarios ( EndToEndLog (..), + aHeadIsInitializingWith, headIsInitializingWith, ) import Hydra.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) -import Hydra.Crypto (HydraKey, SigningKey, generateSigningKey) +import Hydra.Crypto (generateSigningKey) import Hydra.Generator (ClientDataset (..), ClientKeys (..), Dataset (..)) import Hydra.Ledger (txId) import Hydra.Logging (Tracer, traceWith, withTracerOutputTo) +import Hydra.Network (Host) import Hydra.Party (Party, deriveParty) import HydraNode ( HydraClient, @@ -53,7 +55,7 @@ import HydraNode ( waitForAllMatch, waitForNodesConnected, waitMatch, - withConnectionToNode, + withConnectionToNodeHost, withHydraCluster, ) import System.Directory (findExecutable) @@ -104,11 +106,11 @@ benchDemo :: NetworkId -> SocketPath -> NominalDiffTime -> - [SigningKey HydraKey] -> + [Host] -> FilePath -> Dataset -> IO Summary -benchDemo networkId nodeSocket timeoutSeconds hydraKeys workDir dataset@Dataset{clientDatasets, fundingTransaction} = do +benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Dataset{clientDatasets, fundingTransaction} = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> @@ -126,13 +128,18 @@ benchDemo networkId nodeSocket timeoutSeconds hydraKeys workDir dataset@Dataset{ forM_ clientSks (fuelWith100Ada (contramap FromFaucet tracer) node) putStrLn $ "Connecting to hydra cluster in " <> workDir let hydraTracer = contramap FromHydraNode tracer - let parties = Set.fromList (deriveParty <$> hydraKeys) - withConnectionToNode hydraTracer 1 False $ \leader -> - withConnectionToNode hydraTracer 2 False $ \node2 -> - withConnectionToNode hydraTracer 3 False $ \node3 -> do - let followers = [node2, node3] - scenario hydraTracer node workDir dataset parties leader followers + withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case + [] -> error "no hydra clients provided" + (leader : followers) -> + scenario hydraTracer node workDir dataset mempty leader followers where + withHydraClientConnections tracer peers connections action = do + case peers of + [] -> action connections + ((peer, peerId) : rest) -> do + withConnectionToNodeHost tracer peerId peer False $ \con -> do + withHydraClientConnections tracer rest (con : connections) action + returnFaucetFunds tracer node cKeys = do putTextLn "Returning funds to faucet" let faucetTracer = contramap FromFaucet tracer @@ -161,7 +168,9 @@ scenario hydraTracer node workDir dataset@Dataset{clientDatasets, title, descrip send leader $ input "Init" [] headId <- waitForAllMatch (fromIntegral $ 10 * clusterSize) clients $ - headIsInitializingWith parties + if null parties + then aHeadIsInitializingWith (length clients) + else headIsInitializingWith parties putTextLn "Comitting initialUTxO from dataset" expectedUTxO <- commitUTxO node clients dataset diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index da4859c373f..bbf17e1e9eb 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -4,28 +4,9 @@ import Hydra.Prelude import Hydra.Cardano.Api (SocketPath) import Hydra.Chain (maximumNumberOfParties) +import Hydra.Network (Host, readHost) import Hydra.Options (nodeSocketParser) -import Options.Applicative ( - Parser, - ParserInfo, - auto, - command, - fullDesc, - header, - help, - helpDoc, - helper, - hsubparser, - info, - long, - metavar, - option, - progDesc, - short, - str, - strOption, - value, - ) +import Options.Applicative (Parser, ParserInfo, auto, command, fullDesc, header, help, helpDoc, helper, hsubparser, info, long, maybeReader, metavar, option, progDesc, short, str, strOption, value) import Options.Applicative.Builder (argument) import Options.Applicative.Help (Doc, align, fillSep, line, (<+>)) @@ -49,7 +30,7 @@ data Options , scalingFactor :: Int , timeoutSeconds :: NominalDiffTime , nodeSocket :: SocketPath - , hydraSigningKeys :: [FilePath] + , hydraClients :: [Host] } benchOptionsParser :: ParserInfo Options @@ -185,21 +166,19 @@ demoOptionsParser = <*> scalingFactorParser <*> timeoutParser <*> nodeSocketParser - <*> many hydraSigningKeyFileParser - -hydraSigningKeyFileParser :: Parser FilePath -hydraSigningKeyFileParser = - option - str - ( long "hydra-sk" - <> metavar "FILE" - <> help - ( "Hydra signing key of a party in the Head. Can be \ - \provided multiple times, once for each participant (current maximum limit is " - <> show maximumNumberOfParties - <> " )." - ) - ) + <*> many hydraClientsParser + +hydraClientsParser :: Parser Host +hydraClientsParser = + option (maybeReader readHost) $ + long "hydra-client" + <> help + ( "A hydra node api address to connect to. This is using the form :, \ + \where can be an IP address, or a host name. Can be \ + \provided multiple times, once for each participant (current maximum limit is " + <> show maximumNumberOfParties + <> " )." + ) datasetOptionsInfo :: ParserInfo Options datasetOptionsInfo = diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 3d761ee30e3..12b46b38d9b 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -9,11 +9,8 @@ import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) -import Hydra.Cardano.Api (AsType (..)) -import Hydra.Chain.Direct.Util (readFileTextEnvelopeThrow) import Hydra.Cluster.Fixture (Actor (..), defaultNetworkId) import Hydra.Cluster.Util (keysFor) -import Hydra.Crypto (AsType (..)) import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxODemo, generateConstantUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) @@ -39,7 +36,7 @@ main = DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do let action = bench startingNodeId timeoutSeconds run outputDirectory datasetFiles action - DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, nodeSocket, hydraSigningKeys} -> do + DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, nodeSocket, hydraClients} -> do workDir <- createSystemTempDirectory "demo-bench" clientKeys <- do let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] @@ -48,16 +45,15 @@ main = fundsSk <- snd <$> keysFor actorFunds pure $ ClientKeys sk fundsSk forM actors toClientKeys - hydraKeys <- mapM (readFileTextEnvelopeThrow (AsSigningKey AsHydraKey)) hydraSigningKeys - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraKeys + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraClients where - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraKeys = do + playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraClients = do putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize dataset <- genDatasetConstantUTxODemo clientKeys numberOfTxs defaultNetworkId nodeSocket let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset - let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraKeys + let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraClients run outputDirectory [datasetPath] action play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index 3b31782b2ca..cdf9ce28096 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -758,6 +758,14 @@ headIsInitializingWith expectedParties v = do headId <- v ^? key "headId" parseMaybe parseJSON headId +aHeadIsInitializingWith :: Int -> Value -> Maybe HeadId +aHeadIsInitializingWith nbrParties v = do + guard $ v ^? key "tag" == Just "HeadIsInitializing" + parties :: Set Party <- v ^? key "parties" >>= parseMaybe parseJSON + guard $ length parties == nbrParties + headId <- v ^? key "headId" + parseMaybe parseJSON headId + expectErrorStatus :: -- | Expected http status code Int -> diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index 4f918148e0d..fde3340f94a 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -402,7 +402,14 @@ withHydraNode' tracer chainConfig workDir hydraNodeId hydraSKey hydraVKeys allNo ] withConnectionToNode :: forall a. Tracer IO HydraNodeLog -> Int -> Bool -> (HydraClient -> IO a) -> IO a -withConnectionToNode tracer hydraNodeId showHistory action = do +withConnectionToNode tracer hydraNodeId = + withConnectionToNodeHost tracer hydraNodeId Host{hostname, port} + where + hostname = "127.0.0.1" + port = fromInteger $ 4_000 + toInteger hydraNodeId + +withConnectionToNodeHost :: forall a. Tracer IO HydraNodeLog -> Int -> Host -> Bool -> (HydraClient -> IO a) -> IO a +withConnectionToNodeHost tracer hydraNodeId Host{hostname, port} showHistory action = do connectedOnce <- newIORef False tryConnect connectedOnce (200 :: Int) where @@ -421,7 +428,8 @@ withConnectionToNode tracer hydraNodeId showHistory action = do ] historyMode = if showHistory then "/" else "/?history=no" - doConnect connectedOnce = runClient "127.0.0.1" (4_000 + hydraNodeId) historyMode $ \connection -> do + + doConnect connectedOnce = runClient (T.unpack hostname) (fromInteger . toInteger $ port) historyMode $ \connection -> do atomicWriteIORef connectedOnce True traceWith tracer (NodeStarted hydraNodeId) res <- action $ HydraClient{hydraNodeId, connection, tracer} From d1716b7539ebe5440abb068280effa6e48644048 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Wed, 14 Aug 2024 10:33:53 +0200 Subject: [PATCH 017/115] Start a github action for network testing --- .github/workflows/network-test.yaml | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/network-test.yaml diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml new file mode 100644 index 00000000000..c8d2b955dff --- /dev/null +++ b/.github/workflows/network-test.yaml @@ -0,0 +1,33 @@ +name: "Network fault tolerance" + +# TODO: make this a pull_request trigger or so +on: + push: + branches: + - network-testing + +jobs: + network-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: ❄ Prepare nix + uses: cachix/install-nix-action@V27 + with: + extra_nix_config: | + accept-flake-config = true + log-lines = 1000 + + - name: ❄ Cachix cache of nix derivations + uses: cachix/cachix-action@v15 + with: + name: cardano-scaling + authToken: '${{ secrets.CACHIX_CARDANO_SCALING_AUTH_TOKEN }}' + + - name: Docker-based load test + run: | + cd demo + ./run-docker.sh From a4491943aa58e4a9693c08b3d1ab52cb9ec99d2e Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Wed, 14 Aug 2024 10:37:15 +0200 Subject: [PATCH 018/115] Spin up things individually --- .github/workflows/network-test.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index c8d2b955dff..bfb97b1ad45 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -30,4 +30,11 @@ jobs: - name: Docker-based load test run: | cd demo - ./run-docker.sh + ./prepare-devnet.sh + docker compose up -d cardano-node + ./seed-devnet.sh + cat .env # TODO remove + docker compose up -d hydra-node-{1,2,3} + sleep 10 + docker ps + curl 0.0.0.0:4001/snapshot/utxo -v From 9e0041ccb49ea712b5ec831b3d75bbc6308c65ed Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Wed, 14 Aug 2024 10:42:46 +0200 Subject: [PATCH 019/115] Acquire and upload logs --- .github/workflows/network-test.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index bfb97b1ad45..3037c4f1e61 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -38,3 +38,17 @@ jobs: sleep 10 docker ps curl 0.0.0.0:4001/snapshot/utxo -v + + - name: Acquire logs + if: always() + run: | + cd demo + docker compose logs > docker-logs + + - name: 💾 Upload logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: docker-logs + path: demo/docker-logs + if-no-files-found: ignore From 1de6a0aa7dc8a6afaa045e9e6cbecae1c133afd8 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Wed, 14 Aug 2024 10:43:38 +0200 Subject: [PATCH 020/115] Make the hnode fallback in demo/seed-devnet.sh non-interactive tty --- demo/seed-devnet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/seed-devnet.sh b/demo/seed-devnet.sh index 811813de238..6fcc14b557d 100755 --- a/demo/seed-devnet.sh +++ b/demo/seed-devnet.sh @@ -48,7 +48,7 @@ function hnode() { if [[ -n ${HYDRA_NODE_CMD} ]]; then ${HYDRA_NODE_CMD} ${@} else - docker run --rm -it \ + docker run --rm \ --pull always \ -v ${SCRIPT_DIR}/devnet:/devnet \ ghcr.io/cardano-scaling/hydra-node:0.18.1 -- ${@} From 0cc7f8a8280abce5a2fba40e39c0ef7df5f8fcdc Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Wed, 14 Aug 2024 10:59:57 +0200 Subject: [PATCH 021/115] Add invocation to produce traffic --- .github/workflows/network-test.yaml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 3037c4f1e61..6cc4df8876d 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -33,11 +33,30 @@ jobs: ./prepare-devnet.sh docker compose up -d cardano-node ./seed-devnet.sh - cat .env # TODO remove docker compose up -d hydra-node-{1,2,3} sleep 10 docker ps - curl 0.0.0.0:4001/snapshot/utxo -v + + # TODO: make this a flake output / easier accesible + nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ + demo \ + --output-directory=$(pwd)/benchmarks \ + --scaling-factor=100 \ + --timeout=1000s \ + --node-socket=devnet/node.socket \ + --hydra-sk=./alice.sk \ + --hydra-sk=./bob.sk \ + --hydra-sk=./carol.sk + + # TODO: this is maybe rather what we want as this can likely re-use much more code underneath + # datasets datasets/3-nodes.json --timeout 1000s \ + # --node-socket=devnet/node.socket \ + # --hydra-client=localhost:4001 \ + # --hydra-client=localhost:4002 \ + # --hydra-client=localhost:4003 + # + + # TODO: call a fault injection program - name: Acquire logs if: always() From d1a70943eee38ada01dc2417adf080839e407af8 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 10:10:44 +0100 Subject: [PATCH 022/115] check socket permissions --- .github/workflows/network-test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 6cc4df8876d..ee4c7197984 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -37,6 +37,8 @@ jobs: sleep 10 docker ps + la -lah devnet/node.socket + # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ From 04f7724c1ccaee9bf277681e9d8475d7bc08b9e8 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 10:12:05 +0100 Subject: [PATCH 023/115] ls not la --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index ee4c7197984..b959122fca3 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -37,7 +37,7 @@ jobs: sleep 10 docker ps - la -lah devnet/node.socket + ls -lah devnet/node.socket # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ From 8800ea3d2d43af4e8d2263ec1b18d1846aa6d9a4 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 10:15:28 +0100 Subject: [PATCH 024/115] tmate --- .github/workflows/network-test.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index b959122fca3..ba98856b469 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -27,6 +27,14 @@ jobs: name: cardano-scaling authToken: '${{ secrets.CACHIX_CARDANO_SCALING_AUTH_TOKEN }}' + + # TMate hacking + - uses: actions/checkout@v4 + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true + - name: Docker-based load test run: | cd demo @@ -37,8 +45,6 @@ jobs: sleep 10 docker ps - ls -lah devnet/node.socket - # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ From 59d27da58c15e084943c82df3646446b2b1ba591 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 10:25:57 +0100 Subject: [PATCH 025/115] tmate; socket file permissions --- .github/workflows/network-test.yaml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index ba98856b469..bd8003cd589 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -27,13 +27,17 @@ jobs: name: cardano-scaling authToken: '${{ secrets.CACHIX_CARDANO_SCALING_AUTH_TOKEN }}' - - # TMate hacking - - uses: actions/checkout@v4 - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - with: - limit-access-to-actor: true + # Use tmate to get a shell onto the runner to do some temporary hacking + # + # + # + # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug + # + # - uses: actions/checkout@v4 + # - name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + # with: + # limit-access-to-actor: true - name: Docker-based load test run: | @@ -45,6 +49,9 @@ jobs: sleep 10 docker ps + # :tear: socket permissions. + sudo chown runner:docker devnet/node.socket + # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ From 6c8cc69465f31860ca5ed3f1b5a2a79329281580 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 10:53:10 +0100 Subject: [PATCH 026/115] split setup and run benchmarks --- .github/workflows/network-test.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index bd8003cd589..6eda40b1662 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -27,19 +27,7 @@ jobs: name: cardano-scaling authToken: '${{ secrets.CACHIX_CARDANO_SCALING_AUTH_TOKEN }}' - # Use tmate to get a shell onto the runner to do some temporary hacking - # - # - # - # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug - # - # - uses: actions/checkout@v4 - # - name: Setup tmate session - # uses: mxschmitt/action-tmate@v3 - # with: - # limit-access-to-actor: true - - - name: Docker-based load test + - name: Setup containers for network testing run: | cd demo ./prepare-devnet.sh @@ -48,10 +36,22 @@ jobs: docker compose up -d hydra-node-{1,2,3} sleep 10 docker ps - # :tear: socket permissions. sudo chown runner:docker devnet/node.socket + # Use tmate to get a shell onto the runner to do some temporary hacking + # + # + # + # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug + # + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true + + - name: Run the benchmarks + run: | # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ From 7039d7fba53075545d278d0fff080c74e66c34a1 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 11:07:09 +0100 Subject: [PATCH 027/115] Run pumba and the benchmarks - Build and run seperate nix steps --- .github/workflows/network-test.yaml | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 6eda40b1662..26fd4eddd0c 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -45,13 +45,27 @@ jobs: # # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug # - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - with: - limit-access-to-actor: true + # - name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + # with: + # limit-access-to-actor: true + + - name: Build required nix derivations + run: | + nix build .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e + nix build github:noonio/pumba/noon/add-flake - - name: Run the benchmarks + - name: Run pumba and the benchmarks run: | + nix run github:noonio/pumba/noon/add-flake -- \ + -l info \ + --random \ + netem \ + --duration 1m \ + --tc-image gaiadocker/iproute2 \ + loss "re2:hydra" & + + cd demo # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ @@ -69,9 +83,7 @@ jobs: # --hydra-client=localhost:4001 \ # --hydra-client=localhost:4002 \ # --hydra-client=localhost:4003 - # - # TODO: call a fault injection program - name: Acquire logs if: always() From 781200d68bb54631ee1765c35e49fbf6a8429041 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 11:28:21 +0100 Subject: [PATCH 028/115] Line buffering; 100% loss on pumba --- .github/workflows/network-test.yaml | 8 +++++--- hydra-cluster/bench/Main.hs | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 26fd4eddd0c..dc485f8d47e 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -58,12 +58,14 @@ jobs: - name: Run pumba and the benchmarks run: | nix run github:noonio/pumba/noon/add-flake -- \ - -l info \ + -l debug \ --random \ netem \ - --duration 1m \ + --duration 2m \ --tc-image gaiadocker/iproute2 \ - loss "re2:hydra" & + loss \ + --percent 100 \ + "re2:hydra" & cd demo # TODO: make this a flake output / easier accesible diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 12b46b38d9b..8a13cc2e1da 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -20,7 +20,8 @@ import Test.HUnit.Lang (formatFailureReason) import Test.QuickCheck (generate, getSize, scale) main :: IO () -main = +main = do + hSetBuffering stdout LineBuffering execParser benchOptionsParser >>= \case StandaloneOptions{workDirectory = Just workDir, outputDirectory, timeoutSeconds, startingNodeId, scalingFactor, clusterSize} -> do -- XXX: This option is a bit weird as it allows to re-run a test by From 7a5750e6a48b718141eb2451a720a53a3254f278 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 14 Aug 2024 14:08:13 +0100 Subject: [PATCH 029/115] tmate --- .github/workflows/network-test.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index dc485f8d47e..ba881dc958b 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -39,21 +39,21 @@ jobs: # :tear: socket permissions. sudo chown runner:docker devnet/node.socket + - name: Build required nix derivations + run: | + nix build .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e + nix build github:noonio/pumba/noon/add-flake + # Use tmate to get a shell onto the runner to do some temporary hacking # # # # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug # - # - name: Setup tmate session - # uses: mxschmitt/action-tmate@v3 - # with: - # limit-access-to-actor: true - - - name: Build required nix derivations - run: | - nix build .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e - nix build github:noonio/pumba/noon/add-flake + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true - name: Run pumba and the benchmarks run: | From cca50cce8b110ab05f7041bae759784c04524cae Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 14 Aug 2024 23:47:27 +0200 Subject: [PATCH 030/115] Rebase and make use new hydra-client options during CI --- .github/workflows/network-test.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index ba881dc958b..41938ac7188 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -75,9 +75,10 @@ jobs: --scaling-factor=100 \ --timeout=1000s \ --node-socket=devnet/node.socket \ - --hydra-sk=./alice.sk \ - --hydra-sk=./bob.sk \ - --hydra-sk=./carol.sk + --hydra-client=localhost:4001 \ + --hydra-client=localhost:4002 \ + --hydra-client=localhost:4003 + # TODO: this is maybe rather what we want as this can likely re-use much more code underneath # datasets datasets/3-nodes.json --timeout 1000s \ From a8c420fa8a279e53176965bedffde0bc85c179c0 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 15 Aug 2024 11:17:51 +0100 Subject: [PATCH 031/115] Use docker version of pumba It seems to have the right priviledges to do the netem work --- .github/workflows/network-test.yaml | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 41938ac7188..38c5a0da4e9 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -42,7 +42,6 @@ jobs: - name: Build required nix derivations run: | nix build .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e - nix build github:noonio/pumba/noon/add-flake # Use tmate to get a shell onto the runner to do some temporary hacking # @@ -50,22 +49,36 @@ jobs: # # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug # - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - with: - limit-access-to-actor: true + # - name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + # with: + # limit-access-to-actor: true - name: Run pumba and the benchmarks run: | - nix run github:noonio/pumba/noon/add-flake -- \ + docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + gaiaadm/pumba \ + -l debug \ + --random \ + netem \ + --duration 10s \ + --tc-image gaiadocker/iproute2 \ + loss \ + --percent 100 \ + "re2:hydra-node" + + docker run -d --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + gaiaadm/pumba \ -l debug \ --random \ netem \ - --duration 2m \ + --duration 5m \ --tc-image gaiadocker/iproute2 \ loss \ --percent 100 \ - "re2:hydra" & + "re2:hydra-node" cd demo # TODO: make this a flake output / easier accesible From 7d77a2fa9a41a2072ae651598e37778fc8681937 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 15 Aug 2024 14:45:54 +0100 Subject: [PATCH 032/115] Does it work with 20% loss? --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 38c5a0da4e9..ce44401c9f8 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -77,7 +77,7 @@ jobs: --duration 5m \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 100 \ + --percent 20 \ "re2:hydra-node" cd demo From f79dbb47b3705cf0a887a2b99ad21a3bee99f59a Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 15 Aug 2024 14:53:13 +0100 Subject: [PATCH 033/115] See if it succeeds with 0% loss --- .github/workflows/network-test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index ce44401c9f8..157fd4bb740 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -62,7 +62,7 @@ jobs: -l debug \ --random \ netem \ - --duration 10s \ + --duration 1s \ --tc-image gaiadocker/iproute2 \ loss \ --percent 100 \ @@ -77,7 +77,7 @@ jobs: --duration 5m \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 20 \ + --percent 0 \ "re2:hydra-node" cd demo From 69ced3318fa19d1a8d5f76a1c3a3dfd9bc57cc94 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 15 Aug 2024 14:58:39 +0100 Subject: [PATCH 034/115] What about 5% --- .github/workflows/network-test.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 157fd4bb740..813ad46447f 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -42,6 +42,7 @@ jobs: - name: Build required nix derivations run: | nix build .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e + docker pull gaiadocker/iproute2 # Use tmate to get a shell onto the runner to do some temporary hacking # @@ -62,7 +63,7 @@ jobs: -l debug \ --random \ netem \ - --duration 1s \ + --duration 5s \ --tc-image gaiadocker/iproute2 \ loss \ --percent 100 \ @@ -77,7 +78,7 @@ jobs: --duration 5m \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 0 \ + --percent 5 \ "re2:hydra-node" cd demo From c1d15e7c318c1e8e1394e11af3e762c24983d580 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 09:02:06 +0100 Subject: [PATCH 035/115] Optional tmate'ing --- .github/workflows/network-test.yaml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 813ad46447f..e41dfc7e424 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -2,6 +2,13 @@ name: "Network fault tolerance" # TODO: make this a pull_request trigger or so on: + workflow_dispatch: + inputs: + debug_enabled: + type: boolean + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: false push: branches: - network-testing @@ -50,10 +57,11 @@ jobs: # # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug # - # - name: Setup tmate session - # uses: mxschmitt/action-tmate@v3 - # with: - # limit-access-to-actor: true + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + with: + limit-access-to-actor: true - name: Run pumba and the benchmarks run: | From 26998e81d9bddb4b29f72a60e63e853faec10a4d Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 09:21:15 +0100 Subject: [PATCH 036/115] Disable condition as it doesn't work --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index e41dfc7e424..fea7b846b88 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -59,7 +59,7 @@ jobs: # - name: Setup tmate session uses: mxschmitt/action-tmate@v3 - if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + # if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} with: limit-access-to-actor: true From 4fc35a20c5f81a7755bc2e81e45a143a6414283a Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 09:30:07 +0100 Subject: [PATCH 037/115] Try nix again; leave very long running time --- .github/workflows/network-test.yaml | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index fea7b846b88..1a7aaf70c33 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -49,6 +49,7 @@ jobs: - name: Build required nix derivations run: | nix build .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e + nix build github:noonio/pumba/noon/add-flake docker pull gaiadocker/iproute2 # Use tmate to get a shell onto the runner to do some temporary hacking @@ -59,35 +60,20 @@ jobs: # - name: Setup tmate session uses: mxschmitt/action-tmate@v3 - # if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} with: limit-access-to-actor: true - name: Run pumba and the benchmarks run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - gaiaadm/pumba \ - -l debug \ + nix run github:noonio/pumba/noon/add-flake \ + -- -l debug \ --random \ netem \ - --duration 5s \ + --duration 20m \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 100 \ - "re2:hydra-node" - - docker run -d --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - gaiaadm/pumba \ - -l debug \ - --random \ - netem \ - --duration 5m \ - --tc-image gaiadocker/iproute2 \ - loss \ - --percent 5 \ - "re2:hydra-node" + --percent 100 "re2:hydra-node" & cd demo # TODO: make this a flake output / easier accesible From 61b1e8973b188a0bedd9b632b76a286b5505a631 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 09:37:05 +0100 Subject: [PATCH 038/115] Try a sleep --- .github/workflows/network-test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 1a7aaf70c33..4fa8b8f0e03 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -66,6 +66,7 @@ jobs: - name: Run pumba and the benchmarks run: | + sleep 5 nix run github:noonio/pumba/noon/add-flake \ -- -l debug \ --random \ From 9703451d1768f23607d6b9a8547c25f6c240d4df Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 09:41:44 +0100 Subject: [PATCH 039/115] Horrible hack --- .github/workflows/network-test.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 4fa8b8f0e03..63faeaf8913 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -67,6 +67,23 @@ jobs: - name: Run pumba and the benchmarks run: | sleep 5 + + # TODO: a lot of these parameters could come from workflow arguments! + # Maybe we have good defaults, but then allow people to play around. + # - percent + # - scaling-factor + # - ... + # + # HACK: Running it twice :( + nix run github:noonio/pumba/noon/add-flake \ + -- -l debug \ + --random \ + netem \ + --duration 10s \ + --tc-image gaiadocker/iproute2 \ + loss \ + --percent 100 "re2:hydra-node" + nix run github:noonio/pumba/noon/add-flake \ -- -l debug \ --random \ From 9e00319a760e2af584eea489ce4c1a5219aa496a Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 09:49:21 +0100 Subject: [PATCH 040/115] Note about checking for pumba working --- .github/workflows/network-test.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 63faeaf8913..19ab99491fa 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -84,6 +84,11 @@ jobs: loss \ --percent 100 "re2:hydra-node" + # TODO: Could check that pumba is in action here by knowing which peer + # it is targetting and then looking at another peers `server-output` + # (via docker compose cp hydra-node-1:/server-output .) and checking + # for `PeerDisconnected` event. + nix run github:noonio/pumba/noon/add-flake \ -- -l debug \ --random \ From 336ae3f59f6eefe7662a6e6dd7fc0de6eb6a789b Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 09:54:04 +0100 Subject: [PATCH 041/115] Target node 1 for loss and limit target ips for node-2, and node-3 --- .github/workflows/network-test.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 19ab99491fa..3477aaaa3f7 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -80,9 +80,10 @@ jobs: --random \ netem \ --duration 10s \ + --target 172.16.238.30,172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 100 "re2:hydra-node" + --percent 100 "re2:hydra-node-1" # TODO: Could check that pumba is in action here by knowing which peer # it is targetting and then looking at another peers `server-output` @@ -94,9 +95,10 @@ jobs: --random \ netem \ --duration 20m \ + --target 172.16.238.30,172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 100 "re2:hydra-node" & + --percent 100 "re2:hydra-node-1" & cd demo # TODO: make this a flake output / easier accesible From 9ad4b413231c9c7816ba12f07bda008a968ad41a Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 10:35:38 +0100 Subject: [PATCH 042/115] Try /24 --- .github/workflows/network-test.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 3477aaaa3f7..b98a32f60b0 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -80,7 +80,6 @@ jobs: --random \ netem \ --duration 10s \ - --target 172.16.238.30,172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ --percent 100 "re2:hydra-node-1" @@ -95,7 +94,7 @@ jobs: --random \ netem \ --duration 20m \ - --target 172.16.238.30,172.16.238.20 \ + --target 172.16.238.30/24,172.16.238.20/24 \ --tc-image gaiadocker/iproute2 \ loss \ --percent 100 "re2:hydra-node-1" & From 9530f1e33291a610f7985485351acc3b0ba87cca Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 10:39:39 +0100 Subject: [PATCH 043/115] Multiple targets is right --- .github/workflows/network-test.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index b98a32f60b0..b7f2e878231 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -94,7 +94,8 @@ jobs: --random \ netem \ --duration 20m \ - --target 172.16.238.30/24,172.16.238.20/24 \ + --target 172.16.238.30 \ + --target 172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ --percent 100 "re2:hydra-node-1" & From a0e18bcb73688c6c8ae60b2e8089f22f09d2cceb Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 10:44:57 +0100 Subject: [PATCH 044/115] Let's try 2% --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index b7f2e878231..3f69577814a 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -98,7 +98,7 @@ jobs: --target 172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 100 "re2:hydra-node-1" & + --percent 2 "re2:hydra-node-1" & cd demo # TODO: make this a flake output / easier accesible From 9c49594fde09a1d880be16b1f71e799c09c47a18 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 10:50:34 +0100 Subject: [PATCH 045/115] Add now 5% --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 3f69577814a..2bb484fbb73 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -98,7 +98,7 @@ jobs: --target 172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 2 "re2:hydra-node-1" & + --percent 5 "re2:hydra-node-1" & cd demo # TODO: make this a flake output / easier accesible From b66a128cd156f5c358124c380de855e099d2fabd Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 10:56:44 +0100 Subject: [PATCH 046/115] 3% --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 2bb484fbb73..109fccdc826 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -98,7 +98,7 @@ jobs: --target 172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 5 "re2:hydra-node-1" & + --percent 3 "re2:hydra-node-1" & cd demo # TODO: make this a flake output / easier accesible From e6e0e2a5e6743d79e73d77e5562b17190099f943 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 11:05:35 +0100 Subject: [PATCH 047/115] 4% --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 109fccdc826..495d5ced00c 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -98,7 +98,7 @@ jobs: --target 172.16.238.20 \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 3 "re2:hydra-node-1" & + --percent 4 "re2:hydra-node-1" & cd demo # TODO: make this a flake output / easier accesible From 314ec588b951a2e7d7e32510851a81ee1d3f1f5e Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 16 Aug 2024 12:48:22 +0200 Subject: [PATCH 048/115] Try to wait for alice peer disconnected from bob and carol nodes --- .github/workflows/network-test.yaml | 19 ++------------ .github/workflows/network/watch_logs.sh | 34 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/network/watch_logs.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 495d5ced00c..fca532bc344 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -66,29 +66,12 @@ jobs: - name: Run pumba and the benchmarks run: | - sleep 5 - # TODO: a lot of these parameters could come from workflow arguments! # Maybe we have good defaults, but then allow people to play around. # - percent # - scaling-factor # - ... # - # HACK: Running it twice :( - nix run github:noonio/pumba/noon/add-flake \ - -- -l debug \ - --random \ - netem \ - --duration 10s \ - --tc-image gaiadocker/iproute2 \ - loss \ - --percent 100 "re2:hydra-node-1" - - # TODO: Could check that pumba is in action here by knowing which peer - # it is targetting and then looking at another peers `server-output` - # (via docker compose cp hydra-node-1:/server-output .) and checking - # for `PeerDisconnected` event. - nix run github:noonio/pumba/noon/add-flake \ -- -l debug \ --random \ @@ -100,6 +83,8 @@ jobs: loss \ --percent 4 "re2:hydra-node-1" & + .github/workflows/network/watch_logs.sh + cd demo # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ diff --git a/.github/workflows/network/watch_logs.sh b/.github/workflows/network/watch_logs.sh new file mode 100644 index 00000000000..930ce57dcae --- /dev/null +++ b/.github/workflows/network/watch_logs.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -ex + +LOG_FILE_BOB="demo/devnet/persistence/bob/server-output" +LOG_FILE_CAROL="demo/devnet/persistence/carol/server-output" +JQ_EXPRESSION='select(.tag == "PeerDisconnected" and .peer == "1")' + +check_log() { + local log_file=$1 + jq "$JQ_EXPRESSION" "$log_file" | grep -q . +} + +bob_ready=false +carol_ready=false + +while true; do + if ! $bob_ready && check_log "$LOG_FILE_BOB"; then + echo "Match found in Bob's log file!" + bob_ready=true + fi + + if ! $carol_ready && check_log "$LOG_FILE_CAROL"; then + echo "Match found in Carol's log file!" + carol_ready=true + fi + + if $bob_ready && $carol_ready; then + echo "Both conditions met!" + break + fi + + sleep 1 +done From 8dcb837446d6d4f14ac184d58844b7242094ed19 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 16 Aug 2024 11:58:27 +0100 Subject: [PATCH 049/115] Executable; compatible with NixOS --- .github/workflows/network/watch_logs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 .github/workflows/network/watch_logs.sh diff --git a/.github/workflows/network/watch_logs.sh b/.github/workflows/network/watch_logs.sh old mode 100644 new mode 100755 index 930ce57dcae..1217e74cf52 --- a/.github/workflows/network/watch_logs.sh +++ b/.github/workflows/network/watch_logs.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex From a9ff65b3bfd4c5dbe428622e645e6730ef98cbb1 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 16 Aug 2024 13:08:36 +0200 Subject: [PATCH 050/115] Add persitency-dir options to hydra-node Due to the watch_logs script isn't able to find it --- .github/workflows/network-test.yaml | 11 ++++------- .github/workflows/network/watch_logs.sh | 2 +- demo/docker-compose.yaml | 4 +++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index fca532bc344..ab2a99badce 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -77,27 +77,25 @@ jobs: --random \ netem \ --duration 20m \ - --target 172.16.238.30 \ --target 172.16.238.20 \ + --target 172.16.238.30 \ --tc-image gaiadocker/iproute2 \ loss \ - --percent 4 "re2:hydra-node-1" & + --percent 75 "re2:hydra-node-1" & .github/workflows/network/watch_logs.sh - cd demo # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ --output-directory=$(pwd)/benchmarks \ - --scaling-factor=100 \ + --scaling-factor=1 \ --timeout=1000s \ - --node-socket=devnet/node.socket \ + --node-socket=demo/devnet/node.socket \ --hydra-client=localhost:4001 \ --hydra-client=localhost:4002 \ --hydra-client=localhost:4003 - # TODO: this is maybe rather what we want as this can likely re-use much more code underneath # datasets datasets/3-nodes.json --timeout 1000s \ # --node-socket=devnet/node.socket \ @@ -105,7 +103,6 @@ jobs: # --hydra-client=localhost:4002 \ # --hydra-client=localhost:4003 - - name: Acquire logs if: always() run: | diff --git a/.github/workflows/network/watch_logs.sh b/.github/workflows/network/watch_logs.sh index 1217e74cf52..f38e9dc3873 100755 --- a/.github/workflows/network/watch_logs.sh +++ b/.github/workflows/network/watch_logs.sh @@ -30,5 +30,5 @@ while true; do break fi - sleep 1 + sleep 5 done diff --git a/demo/docker-compose.yaml b/demo/docker-compose.yaml index 139ff66767d..b541ff481b2 100644 --- a/demo/docker-compose.yaml +++ b/demo/docker-compose.yaml @@ -48,6 +48,7 @@ services: , "--ledger-protocol-parameters", "/devnet/protocol-parameters.json" , "--testnet-magic", "42" , "--node-socket", "/devnet/node.socket" + , "--persistence-dir", "/devnet/persistence/alice" ] networks: hydra_net: @@ -83,6 +84,7 @@ services: , "--ledger-protocol-parameters", "/devnet/protocol-parameters.json" , "--testnet-magic", "42" , "--node-socket", "/devnet/node.socket" + , "--persistence-dir", "/devnet/persistence/bob" ] networks: hydra_net: @@ -118,6 +120,7 @@ services: , "--ledger-protocol-parameters", "/devnet/protocol-parameters.json" , "--testnet-magic", "42" , "--node-socket", "/devnet/node.socket" + , "--persistence-dir", "/devnet/persistence/carol" ] networks: hydra_net: @@ -188,7 +191,6 @@ services: hydra_net: ipv4_address: 172.16.238.5 - networks: hydra_net: driver: bridge From 88e5bce09f1df0805a00f5fc1fcc807b96390781 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Mon, 19 Aug 2024 11:32:39 +0100 Subject: [PATCH 051/115] Use a special image built for netem; no more tcimage --- .github/workflows/network-test.yaml | 14 ++++++++++---- demo/docker-compose-netem.yaml | 9 +++++++++ nix/hydra/docker.nix | 13 +++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 demo/docker-compose-netem.yaml diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index ab2a99badce..0150d724fd2 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -34,23 +34,30 @@ jobs: name: cardano-scaling authToken: '${{ secrets.CACHIX_CARDANO_SCALING_AUTH_TOKEN }}' + + - name: Build docker images for netem specifically + run: | + nix build .#docker-hydra-node-for-netem + ./result | docker load + - name: Setup containers for network testing run: | cd demo ./prepare-devnet.sh docker compose up -d cardano-node ./seed-devnet.sh - docker compose up -d hydra-node-{1,2,3} + # Specify two docker compose yamls; the second one overrides the + # images to use the netem ones specifically + docker compose -f docker-compose.yaml -f docker-compose-netem.yaml up -d hydra-node-{1,2,3} sleep 10 docker ps # :tear: socket permissions. sudo chown runner:docker devnet/node.socket - - name: Build required nix derivations + - name: Build required nix and docker derivations run: | nix build .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e nix build github:noonio/pumba/noon/add-flake - docker pull gaiadocker/iproute2 # Use tmate to get a shell onto the runner to do some temporary hacking # @@ -79,7 +86,6 @@ jobs: --duration 20m \ --target 172.16.238.20 \ --target 172.16.238.30 \ - --tc-image gaiadocker/iproute2 \ loss \ --percent 75 "re2:hydra-node-1" & diff --git a/demo/docker-compose-netem.yaml b/demo/docker-compose-netem.yaml new file mode 100644 index 00000000000..dcf1f850572 --- /dev/null +++ b/demo/docker-compose-netem.yaml @@ -0,0 +1,9 @@ +services: + hydra-node-1: + image: hydra-node-for-netem + + hydra-node-2: + image: hydra-node-for-netem + + hydra-node-3: + image: hydra-node-for-netem diff --git a/nix/hydra/docker.nix b/nix/hydra/docker.nix index cd0d6cfca66..8f34730f5dc 100644 --- a/nix/hydra/docker.nix +++ b/nix/hydra/docker.nix @@ -18,6 +18,19 @@ in }; }; + hydra-node-for-netem = pkgs.dockerTools.streamLayeredImage { + name = "hydra-node-for-netem"; + tag = "latest"; + created = "now"; + contents = [ + pkgs.iproute2 + pkgs.busybox + ]; + config = { + Entrypoint = [ "${hydraPackages.hydra-node-static}/bin/hydra-node" ]; + }; + }; + hydra-tui = pkgs.dockerTools.streamLayeredImage { name = "hydra-tui"; tag = "latest"; From 080d9156780b68951fbd049a073d135eb1b216dd Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 19 Aug 2024 16:56:00 +0200 Subject: [PATCH 052/115] Add base network testing readme docs --- hydra-cluster/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hydra-cluster/README.md b/hydra-cluster/README.md index b5433128703..2638a7cf77b 100644 --- a/hydra-cluster/README.md +++ b/hydra-cluster/README.md @@ -140,3 +140,14 @@ The benchmark can be run in two modes corresponding to two different commands: * `datasets`: Runs one or more preexisting _datasets_ in sequence and collect their results in a single markdown formatted file. This is useful to track the evolution of hydra-node's performance over some well-known datasets over time and produce a human-readable summary. Check out `cabal bench --benchmark-options --help` for more details. + +# Network Testing + +The benchmark can be also run over the running `demo` hydra-cluster, using `cabal bench` and produces a +`results.csv` file in a work directory. Same as for benchmarks results, you can use the `bench/plot.sh` script to plot the transaction confirmation times. + +To run the benchmark in this mode, the command is: +* `demo`: Runs one or more generated _datasets_ in sequence and collect their results in a single markdown formatted file. The purpose of this setup is to facilitate a variaty of network-resiliance scenarios, such as packet loss or node failures. This is useful to prove the robustness and performance of the hydra-node's network over time and produce a human-readable summary. + +For instance, we make use of this in our [CI](https://github.com/cardano-scaling/hydra/blob/master/.github/workflows/network-test.yaml) to keep track for scenarios that we care about. + From 629061556eff81b61c10ab821fcce267605de820 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 19 Aug 2024 17:19:11 +0200 Subject: [PATCH 053/115] Use local functions to seed the network Instead of exposing private ones defined in where clauses. --- hydra-cluster/bench/Bench/EndToEnd.hs | 36 ++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index fa470654376..82149001fe1 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -124,9 +124,13 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas let clientSks = clientKeys <$> clientDatasets (`finally` returnFaucetFunds tracer node clientSks) $ do putTextLn "Seeding network" - fundClients networkId nodeSocket fundingTransaction - forM_ clientSks (fuelWith100Ada (contramap FromFaucet tracer) node) - putStrLn $ "Connecting to hydra cluster in " <> workDir + submitTransaction networkId nodeSocket fundingTransaction + void $ awaitTransaction networkId nodeSocket fundingTransaction + forM_ clientSks $ \ClientKeys{signingKey} -> do + let vk = getVerificationKey signingKey + putTextLn $ "Seed client " <> show vk + seedFromFaucet node vk 100_000_000 (contramap FromFaucet tracer) + putStrLn $ "Connecting to hydra cluster: " <> show hydraClients let hydraTracer = contramap FromHydraNode tracer withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case [] -> error "no hydra clients provided" @@ -306,22 +310,20 @@ movingAverage confirmations = -- transaction is returned. seedNetwork :: RunningNode -> Dataset -> Tracer IO FaucetLog -> IO TxId seedNetwork node@RunningNode{nodeSocket, networkId} Dataset{fundingTransaction, clientDatasets} tracer = do - fundClients networkId nodeSocket fundingTransaction - forM_ (clientKeys <$> clientDatasets) (fuelWith100Ada tracer node) + fundClients + forM_ (clientKeys <$> clientDatasets) fuelWith100Ada putTextLn "Publishing hydra scripts" publishHydraScriptsAs node Faucet - -fundClients :: NetworkId -> SocketPath -> Tx -> IO () -fundClients networkId nodeSocket fundingTransaction = do - putTextLn "Fund scenario from faucet" - submitTransaction networkId nodeSocket fundingTransaction - void $ awaitTransaction networkId nodeSocket fundingTransaction - -fuelWith100Ada :: Tracer IO FaucetLog -> RunningNode -> ClientKeys -> IO UTxO -fuelWith100Ada tracer node ClientKeys{signingKey} = do - let vk = getVerificationKey signingKey - putTextLn $ "Seed client " <> show vk - seedFromFaucet node vk 100_000_000 tracer + where + fundClients = do + putTextLn "Fund scenario from faucet" + submitTransaction networkId nodeSocket fundingTransaction + void $ awaitTransaction networkId nodeSocket fundingTransaction + + fuelWith100Ada ClientKeys{signingKey} = do + let vk = getVerificationKey signingKey + putTextLn $ "Seed client " <> show vk + seedFromFaucet node vk 100_000_000 tracer -- | Commit all (expected to exit) 'initialUTxO' from the dataset using the -- (asumed same sequence) of clients. From de1054976a587c02b649e1aafa9c5f5e6c2a40a6 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 20 Aug 2024 11:16:35 +0200 Subject: [PATCH 054/115] Do not seed neetwork before runnin bench-demo Because the script will be seeding the network. The reason for this its because the scripts does not rely on a genesis funding transaction. --- .github/workflows/network-test.yaml | 4 +- demo/publish-script-devnet.sh | 81 +++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100755 demo/publish-script-devnet.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 0150d724fd2..4c55b1a24b4 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -11,7 +11,7 @@ on: default: false push: branches: - - network-testing + - demo-bench jobs: network-test: @@ -45,7 +45,7 @@ jobs: cd demo ./prepare-devnet.sh docker compose up -d cardano-node - ./seed-devnet.sh + ./publish-script-devnet.sh # Specify two docker compose yamls; the second one overrides the # images to use the netem ones specifically docker compose -f docker-compose.yaml -f docker-compose-netem.yaml up -d hydra-node-{1,2,3} diff --git a/demo/publish-script-devnet.sh b/demo/publish-script-devnet.sh new file mode 100755 index 00000000000..aec499bb0ed --- /dev/null +++ b/demo/publish-script-devnet.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# Seed a "devnet" by distributing Ada to hydra nodes +set -eo pipefail + +SCRIPT_DIR=${SCRIPT_DIR:-$(realpath $(dirname $(realpath $0)))} +NETWORK_ID=42 + +CCLI_CMD= +DEVNET_DIR=/devnet +if [[ -n ${1} ]]; then + echo >&2 "Using provided cardano-cli command: ${1}" + $(${1} version > /dev/null) + CCLI_CMD=${1} + DEVNET_DIR=${SCRIPT_DIR}/devnet +fi + +HYDRA_NODE_CMD= +if [[ -n ${2} ]]; then + echo >&2 "Using provided hydra-node command: ${2}" + ${2} --version > /dev/null + HYDRA_NODE_CMD=${2} +fi + +DOCKER_COMPOSE_CMD= +if [[ ! -x ${CCLI_CMD} ]]; then + if docker compose --version > /dev/null 2>&1; then + DOCKER_COMPOSE_CMD="docker compose" + else + DOCKER_COMPOSE_CMD="docker-compose" + fi +fi + +# Invoke cardano-cli in running cardano-node container or via provided cardano-cli +function ccli() { + ccli_ ${@} --testnet-magic ${NETWORK_ID} +} +function ccli_() { + if [[ -x ${CCLI_CMD} ]]; then + ${CCLI_CMD} ${@} + else + ${DOCKER_COMPOSE_CMD} exec cardano-node cardano-cli ${@} + fi +} + +# Invoke hydra-node in a container or via provided executable +function hnode() { + if [[ -n ${HYDRA_NODE_CMD} ]]; then + ${HYDRA_NODE_CMD} ${@} + else + docker run --rm \ + --pull always \ + -v ${SCRIPT_DIR}/devnet:/devnet \ + ghcr.io/cardano-scaling/hydra-node:0.18.1 -- ${@} + fi +} + +function publishReferenceScripts() { + echo >&2 "Publishing reference scripts..." + hnode publish-scripts \ + --testnet-magic ${NETWORK_ID} \ + --node-socket ${DEVNET_DIR}/node.socket \ + --cardano-signing-key devnet/credentials/faucet.sk +} + +function queryPParams() { + echo >&2 "Query Protocol parameters" + if [[ -x ${CCLI_CMD} ]]; then + ccli query protocol-parameters --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ + | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json + else + docker exec demo-cardano-node-1 cardano-cli query protocol-parameters --testnet-magic ${NETWORK_ID} --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ + | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json + fi + echo >&2 "Saved in protocol-parameters.json" +} + +queryPParams +echo "HYDRA_SCRIPTS_TX_ID=$(publishReferenceScripts)" > .env +echo >&2 "Environment variable stored in '.env'" +echo >&2 -e "\n\t$(cat .env)\n" From 1c086e9647e7edbcaee1f2025b48fd0737199e94 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 20 Aug 2024 11:37:10 +0200 Subject: [PATCH 055/115] Remove FIXME as its not reproduceable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It just simply does not break ¯\_(ツ)_/¯ --- hydra-cluster/test/Test/GeneratorSpec.hs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index 1dec4a5a9c7..6ea93e7f692 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -7,7 +7,6 @@ import Test.Hydra.Prelude import Data.Text (unpack) import Hydra.Cardano.Api (LedgerEra, UTxO, prettyPrintJSON, utxoFromTx) -import Hydra.Chain (maximumNumberOfParties) import Hydra.Chain.Direct.Fixture (defaultGlobals, defaultPParams) import Hydra.Cluster.Fixture (Actor (Faucet)) import Hydra.Cluster.Util (keysFor) @@ -34,8 +33,7 @@ spec = parallel $ do prop_keepsUTxOConstant :: Property prop_keepsUTxOConstant = - -- FIXME: Breaks with values > 33. Why? - forAll (inputs maximumNumberOfParties) $ \(Positive n, clientKeys) -> do + forAll arbitrary $ \(Positive n, clientKeys) -> do idempotentIOProperty $ do faucetSk <- snd <$> keysFor Faucet @@ -54,11 +52,6 @@ prop_keepsUTxOConstant = & counterexample ("utxo: " <> prettyJSONString initialUTxO) & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) pure $ conjoin allProperties - where - inputs nClients = do - n <- arbitrary - clientKeys <- replicateM nClients arbitrary - pure (n, clientKeys) apply :: Globals -> LedgerEnv LedgerEra -> UTxO -> Tx -> UTxO apply globals ledgerEnv utxo tx = From 58eab02ebb831a96ebbcdc2f017b9b26d998b0ad Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 20 Aug 2024 11:54:15 +0200 Subject: [PATCH 056/115] Minor refactor on generateOneTransfer So that is reusable for this use-case without braking others. --- hydra-cluster/src/Hydra/Generator.hs | 5 ++-- hydra-node/src/Hydra/Ledger/Cardano.hs | 34 ++++++++++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 99c65b4fe64..97b77d7cf97 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -15,7 +15,7 @@ import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (Faucet), availableInitialFunds) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) -import Hydra.Ledger.Cardano (genSigningKey, generateOneTransfer) +import Hydra.Ledger.Cardano (genSigningKey, generateOneSelfTransfer) import Test.QuickCheck (choose, generate, sized, vectorOf) networkId :: NetworkId @@ -111,7 +111,6 @@ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction = do where generateClientDataset clientKeys@ClientKeys{externalSigningKey} = do let vk = getVerificationKey externalSigningKey - keyPair = (vk, externalSigningKey) -- NOTE: The initialUTxO must all UTXO we will later commit. We assume -- that everything owned by the externalSigningKey will get committed -- into the head. @@ -121,7 +120,7 @@ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction = do txSequence <- reverse . thrd - <$> foldM (generateOneTransfer networkId) (initialUTxO, keyPair, []) [1 .. nTxs] + <$> foldM (generateOneSelfTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] pure ClientDataset{clientKeys, initialUTxO, txSequence} thrd (_, _, c) = c diff --git a/hydra-node/src/Hydra/Ledger/Cardano.hs b/hydra-node/src/Hydra/Ledger/Cardano.hs index 7214a0fc468..04bbf60be0e 100644 --- a/hydra-node/src/Hydra/Ledger/Cardano.hs +++ b/hydra-node/src/Hydra/Ledger/Cardano.hs @@ -282,31 +282,51 @@ genSequenceOfSimplePaymentTransactions = do genFixedSizeSequenceOfSimplePaymentTransactions :: Int -> Gen (UTxO, [Tx]) genFixedSizeSequenceOfSimplePaymentTransactions numTxs = do - keyPair@(vk, _) <- genKeyPair + (vk, sk) <- genKeyPair utxo <- genOneUTxOFor vk txs <- reverse . thrd - <$> foldM (generateOneTransfer testNetworkId) (utxo, keyPair, []) [1 .. numTxs] + <$> foldM (generateOneRandomTransfer testNetworkId) (utxo, sk, []) [1 .. numTxs] pure (utxo, txs) where thrd (_, _, c) = c testNetworkId = Testnet $ NetworkMagic 42 +generateOneRandomTransfer :: + NetworkId -> + (UTxO, SigningKey PaymentKey, [Tx]) -> + Int -> + Gen (UTxO, SigningKey PaymentKey, [Tx]) +generateOneRandomTransfer networkId senderUtxO nbrTx = do + recipient <- genKeyPair + generateOneTransfer networkId (snd recipient) senderUtxO nbrTx + +generateOneSelfTransfer :: + NetworkId -> + (UTxO, SigningKey PaymentKey, [Tx]) -> + Int -> + Gen (UTxO, SigningKey PaymentKey, [Tx]) +generateOneSelfTransfer networkId senderUtxO nbrTx = do + let (_, recipientSk, _) = senderUtxO + generateOneTransfer networkId recipientSk senderUtxO nbrTx + generateOneTransfer :: NetworkId -> - (UTxO, (VerificationKey PaymentKey, SigningKey PaymentKey), [Tx]) -> + SigningKey PaymentKey -> + (UTxO, SigningKey PaymentKey, [Tx]) -> Int -> - Gen (UTxO, (VerificationKey PaymentKey, SigningKey PaymentKey), [Tx]) -generateOneTransfer networkId (utxo, recipient@(_, sender), txs) _ = do + Gen (UTxO, SigningKey PaymentKey, [Tx]) +generateOneTransfer networkId recipientSk (utxo, sender, txs) _ = do + let recipientVk = getVerificationKey recipientSk -- NOTE(AB): elements is partial, it crashes if given an empty list, We don't expect -- this function to be ever used in production, and crash will be caught in tests case UTxO.pairs utxo of [txin] -> - case mkSimpleTx txin (mkVkAddress networkId (fst recipient), balance @Tx utxo) sender of + case mkSimpleTx txin (mkVkAddress networkId recipientVk, balance @Tx utxo) sender of Left e -> error $ "Tx construction failed: " <> show e <> ", utxo: " <> show utxo Right tx -> - pure (utxoFromTx tx, recipient, tx : txs) + pure (utxoFromTx tx, recipientSk, tx : txs) _ -> error "Couldn't generate transaction sequence: need exactly one UTXO." From 088d66156335bbbcb86004829d29d551965a868a Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 20 Aug 2024 11:28:33 +0100 Subject: [PATCH 057/115] Use random/self transfer generator functions --- hydra-cluster/src/Hydra/Generator.hs | 13 +++++++------ hydra-cluster/test/Test/GeneratorSpec.hs | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 97b77d7cf97..44f5a735a18 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -15,7 +15,7 @@ import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (Faucet), availableInitialFunds) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) -import Hydra.Ledger.Cardano (genSigningKey, generateOneSelfTransfer) +import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer) import Test.QuickCheck (choose, generate, sized, vectorOf) networkId :: NetworkId @@ -40,7 +40,7 @@ instance Arbitrary Dataset where let nClients = max 1 (min maximumNumberOfParties (n `div` 10)) clientKeys <- vectorOf nClients arbitrary fundingTransaction <- makeGenesisFundingTx faucetSk clientKeys - genDatasetConstantUTxO clientKeys n fundingTransaction + genDatasetConstantUTxO clientKeys n fundingTransaction generateOneSelfTransfer data ClientKeys = ClientKeys { signingKey :: SigningKey PaymentKey @@ -96,7 +96,7 @@ generateConstantUTxODataset nClients nTxs = do (_, faucetSk) <- keysFor Faucet clientKeys <- generate $ replicateM nClients arbitrary fundingTransaction <- generate $ makeGenesisFundingTx faucetSk clientKeys - generate $ genDatasetConstantUTxO clientKeys nTxs fundingTransaction + generate $ genDatasetConstantUTxO clientKeys nTxs fundingTransaction generateOneRandomTransfer genDatasetConstantUTxO :: -- | Clients @@ -104,8 +104,9 @@ genDatasetConstantUTxO :: -- | Number of transactions Int -> Tx -> + (NetworkId -> (UTxO, SigningKey PaymentKey, [Tx]) -> Int -> Gen (UTxO, SigningKey PaymentKey, [Tx])) -> Gen Dataset -genDatasetConstantUTxO allClientKeys nTxs fundingTransaction = do +genDatasetConstantUTxO allClientKeys nTxs fundingTransaction tfr = do clientDatasets <- forM allClientKeys generateClientDataset pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where @@ -120,7 +121,7 @@ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction = do txSequence <- reverse . thrd - <$> foldM (generateOneSelfTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] + <$> foldM (tfr networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] pure ClientDataset{clientKeys, initialUTxO, txSequence} thrd (_, _, c) = c @@ -184,4 +185,4 @@ genDatasetConstantUTxODemo allClientKeys nTxs networkId' nodeSocket = do Right body -> do let signedTx = sign faucetSk body pure signedTx - generate $ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction + generate $ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction generateOneSelfTransfer diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index 6ea93e7f692..0e3989173c6 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -17,7 +17,7 @@ import Hydra.Generator ( makeGenesisFundingTx, ) import Hydra.Ledger (ChainSlot (ChainSlot), applyTransactions) -import Hydra.Ledger.Cardano (Tx, cardanoLedger) +import Hydra.Ledger.Cardano (Tx, cardanoLedger, generateOneSelfTransfer) import Hydra.Ledger.Cardano.Configuration ( Globals, LedgerEnv, @@ -42,7 +42,7 @@ prop_keepsUTxOConstant = -- XXX: non-exhaustive pattern match pure $ forAll (makeGenesisFundingTx faucetSk clientKeys) $ \fundingTransaction -> do - Dataset{clientDatasets} <- genDatasetConstantUTxO clientKeys n fundingTransaction + Dataset{clientDatasets} <- genDatasetConstantUTxO clientKeys n fundingTransaction generateOneSelfTransfer allProperties <- forM clientDatasets $ \ClientDataset{txSequence} -> do let initialUTxO = utxoFromTx fundingTransaction finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence From e48b67ac2f33aedc705b2974cb2235085641edb6 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 20 Aug 2024 20:15:29 +0200 Subject: [PATCH 058/115] Update demo-bech to take datasets from args --- .github/workflows/network-test.yaml | 14 +-- demo/publish-script-devnet.sh | 81 ---------------- hydra-cluster/README.md | 2 +- hydra-cluster/bench/Bench/EndToEnd.hs | 38 ++------ hydra-cluster/bench/Bench/Options.hs | 8 +- hydra-cluster/bench/Main.hs | 27 +----- hydra-cluster/src/Hydra/Generator.hs | 113 +++++++---------------- hydra-cluster/test/Test/GeneratorSpec.hs | 29 +++--- 8 files changed, 70 insertions(+), 242 deletions(-) delete mode 100755 demo/publish-script-devnet.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 4c55b1a24b4..2aaf90d63c4 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -45,7 +45,7 @@ jobs: cd demo ./prepare-devnet.sh docker compose up -d cardano-node - ./publish-script-devnet.sh + ./seed-devnet.sh # Specify two docker compose yamls; the second one overrides the # images to use the netem ones specifically docker compose -f docker-compose.yaml -f docker-compose-netem.yaml up -d hydra-node-{1,2,3} @@ -91,24 +91,20 @@ jobs: .github/workflows/network/watch_logs.sh + # FIXME! generate dataset + touch datasets/3-nodes-demo.json + # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ + datasets/3-nodes-demo.json \ --output-directory=$(pwd)/benchmarks \ - --scaling-factor=1 \ --timeout=1000s \ --node-socket=demo/devnet/node.socket \ --hydra-client=localhost:4001 \ --hydra-client=localhost:4002 \ --hydra-client=localhost:4003 - # TODO: this is maybe rather what we want as this can likely re-use much more code underneath - # datasets datasets/3-nodes.json --timeout 1000s \ - # --node-socket=devnet/node.socket \ - # --hydra-client=localhost:4001 \ - # --hydra-client=localhost:4002 \ - # --hydra-client=localhost:4003 - - name: Acquire logs if: always() run: | diff --git a/demo/publish-script-devnet.sh b/demo/publish-script-devnet.sh deleted file mode 100755 index aec499bb0ed..00000000000 --- a/demo/publish-script-devnet.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -# Seed a "devnet" by distributing Ada to hydra nodes -set -eo pipefail - -SCRIPT_DIR=${SCRIPT_DIR:-$(realpath $(dirname $(realpath $0)))} -NETWORK_ID=42 - -CCLI_CMD= -DEVNET_DIR=/devnet -if [[ -n ${1} ]]; then - echo >&2 "Using provided cardano-cli command: ${1}" - $(${1} version > /dev/null) - CCLI_CMD=${1} - DEVNET_DIR=${SCRIPT_DIR}/devnet -fi - -HYDRA_NODE_CMD= -if [[ -n ${2} ]]; then - echo >&2 "Using provided hydra-node command: ${2}" - ${2} --version > /dev/null - HYDRA_NODE_CMD=${2} -fi - -DOCKER_COMPOSE_CMD= -if [[ ! -x ${CCLI_CMD} ]]; then - if docker compose --version > /dev/null 2>&1; then - DOCKER_COMPOSE_CMD="docker compose" - else - DOCKER_COMPOSE_CMD="docker-compose" - fi -fi - -# Invoke cardano-cli in running cardano-node container or via provided cardano-cli -function ccli() { - ccli_ ${@} --testnet-magic ${NETWORK_ID} -} -function ccli_() { - if [[ -x ${CCLI_CMD} ]]; then - ${CCLI_CMD} ${@} - else - ${DOCKER_COMPOSE_CMD} exec cardano-node cardano-cli ${@} - fi -} - -# Invoke hydra-node in a container or via provided executable -function hnode() { - if [[ -n ${HYDRA_NODE_CMD} ]]; then - ${HYDRA_NODE_CMD} ${@} - else - docker run --rm \ - --pull always \ - -v ${SCRIPT_DIR}/devnet:/devnet \ - ghcr.io/cardano-scaling/hydra-node:0.18.1 -- ${@} - fi -} - -function publishReferenceScripts() { - echo >&2 "Publishing reference scripts..." - hnode publish-scripts \ - --testnet-magic ${NETWORK_ID} \ - --node-socket ${DEVNET_DIR}/node.socket \ - --cardano-signing-key devnet/credentials/faucet.sk -} - -function queryPParams() { - echo >&2 "Query Protocol parameters" - if [[ -x ${CCLI_CMD} ]]; then - ccli query protocol-parameters --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ - | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json - else - docker exec demo-cardano-node-1 cardano-cli query protocol-parameters --testnet-magic ${NETWORK_ID} --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ - | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json - fi - echo >&2 "Saved in protocol-parameters.json" -} - -queryPParams -echo "HYDRA_SCRIPTS_TX_ID=$(publishReferenceScripts)" > .env -echo >&2 "Environment variable stored in '.env'" -echo >&2 -e "\n\t$(cat .env)\n" diff --git a/hydra-cluster/README.md b/hydra-cluster/README.md index 2638a7cf77b..f393a40f5e3 100644 --- a/hydra-cluster/README.md +++ b/hydra-cluster/README.md @@ -147,7 +147,7 @@ The benchmark can be also run over the running `demo` hydra-cluster, using `caba `results.csv` file in a work directory. Same as for benchmarks results, you can use the `bench/plot.sh` script to plot the transaction confirmation times. To run the benchmark in this mode, the command is: -* `demo`: Runs one or more generated _datasets_ in sequence and collect their results in a single markdown formatted file. The purpose of this setup is to facilitate a variaty of network-resiliance scenarios, such as packet loss or node failures. This is useful to prove the robustness and performance of the hydra-node's network over time and produce a human-readable summary. +* `demo`: Runs one or more preexisting _datasets_ in sequence and collect their results in a single markdown formatted file. The purpose of this setup is to facilitate a variaty of network-resiliance scenarios, such as packet loss or node failures. This is useful to prove the robustness and performance of the hydra-node's network over time and produce a human-readable summary. For instance, we make use of this in our [CI](https://github.com/cardano-scaling/hydra/blob/master/.github/workflows/network-test.yaml) to keep track for scenarios that we care about. diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 82149001fe1..145a32dd99c 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -29,7 +29,7 @@ import Data.Set ((\\)) import Data.Set qualified as Set import Data.Time (UTCTime (UTCTime), utctDayTime) import Hydra.Cardano.Api (NetworkId, SocketPath, Tx, TxId, UTxO, getVerificationKey, signTx) -import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, returnFundsToFaucet', seedFromFaucet) +import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, seedFromFaucet) import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Scenarios ( EndToEndLog (..), @@ -40,7 +40,7 @@ import Hydra.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) import Hydra.Crypto (generateSigningKey) import Hydra.Generator (ClientDataset (..), ClientKeys (..), Dataset (..)) import Hydra.Ledger (txId) -import Hydra.Logging (Tracer, traceWith, withTracerOutputTo) +import Hydra.Logging (Tracer, withTracerOutputTo) import Hydra.Network (Host) import Hydra.Party (Party, deriveParty) import HydraNode ( @@ -110,7 +110,7 @@ benchDemo :: FilePath -> Dataset -> IO Summary -benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Dataset{clientDatasets, fundingTransaction} = do +benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> @@ -121,21 +121,12 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas Nothing -> error ("Not found running node at socket: " <> show nodeSocket <> ", and network: " <> show networkId) Just node -> do - let clientSks = clientKeys <$> clientDatasets - (`finally` returnFaucetFunds tracer node clientSks) $ do - putTextLn "Seeding network" - submitTransaction networkId nodeSocket fundingTransaction - void $ awaitTransaction networkId nodeSocket fundingTransaction - forM_ clientSks $ \ClientKeys{signingKey} -> do - let vk = getVerificationKey signingKey - putTextLn $ "Seed client " <> show vk - seedFromFaucet node vk 100_000_000 (contramap FromFaucet tracer) - putStrLn $ "Connecting to hydra cluster: " <> show hydraClients - let hydraTracer = contramap FromHydraNode tracer - withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case - [] -> error "no hydra clients provided" - (leader : followers) -> - scenario hydraTracer node workDir dataset mempty leader followers + putStrLn $ "Connecting to hydra cluster: " <> show hydraClients + let hydraTracer = contramap FromHydraNode tracer + withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case + [] -> error "no hydra clients provided" + (leader : followers) -> + scenario hydraTracer node workDir dataset mempty leader followers where withHydraClientConnections tracer peers connections action = do case peers of @@ -144,17 +135,6 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas withConnectionToNodeHost tracer peerId peer False $ \con -> do withHydraClientConnections tracer rest (con : connections) action - returnFaucetFunds tracer node cKeys = do - putTextLn "Returning funds to faucet" - let faucetTracer = contramap FromFaucet tracer - let senders = concatMap @[] (\(ClientKeys sk esk) -> [sk, esk]) cKeys - mapM_ - ( \sender -> do - returnAmount <- returnFundsToFaucet' faucetTracer node sender - traceWith faucetTracer $ ReturnedFunds{actor = show sender, returnAmount} - ) - senders - scenario :: Tracer IO HydraNodeLog -> RunningNode -> diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index bbf17e1e9eb..379828f9c7c 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -26,8 +26,8 @@ data Options , startingNodeId :: Int } | DemoOptions - { outputDirectory :: Maybe FilePath - , scalingFactor :: Int + { datasetFiles :: [FilePath] + , outputDirectory :: Maybe FilePath , timeoutSeconds :: NominalDiffTime , nodeSocket :: SocketPath , hydraClients :: [Host] @@ -162,8 +162,8 @@ demoOptionsInfo = demoOptionsParser :: Parser Options demoOptionsParser = DemoOptions - <$> optional outputDirectoryParser - <*> scalingFactorParser + <$> many filepathParser + <*> optional outputDirectoryParser <*> timeoutParser <*> nodeSocketParser <*> many hydraClientsParser diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 8a13cc2e1da..403a18970fb 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -9,9 +9,8 @@ import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) -import Hydra.Cluster.Fixture (Actor (..), defaultNetworkId) -import Hydra.Cluster.Util (keysFor) -import Hydra.Generator (ClientKeys (..), Dataset (..), genDatasetConstantUTxODemo, generateConstantUTxODataset) +import Hydra.Cluster.Fixture (defaultNetworkId) +import Hydra.Generator (Dataset (..), generateConstantUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) @@ -37,26 +36,10 @@ main = do DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do let action = bench startingNodeId timeoutSeconds run outputDirectory datasetFiles action - DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, nodeSocket, hydraClients} -> do - workDir <- createSystemTempDirectory "demo-bench" - clientKeys <- do - let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] - let toClientKeys (actor, actorFunds) = do - sk <- snd <$> keysFor actor - fundsSk <- snd <$> keysFor actorFunds - pure $ ClientKeys sk fundsSk - forM actors toClientKeys - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraClients + DemoOptions{datasetFiles, outputDirectory, timeoutSeconds, nodeSocket, hydraClients} -> do + let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraClients + run outputDirectory datasetFiles action where - playDemo outputDirectory timeoutSeconds scalingFactor clientKeys workDir nodeSocket hydraClients = do - putStrLn $ "Generating single dataset in work directory: " <> workDir - numberOfTxs <- generate $ scale (* scalingFactor) getSize - dataset <- genDatasetConstantUTxODemo clientKeys numberOfTxs defaultNetworkId nodeSocket - let datasetPath = workDir "dataset.json" - saveDataset datasetPath dataset - let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraClients - run outputDirectory [datasetPath] action - play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 44f5a735a18..45662642add 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,18 +5,14 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (buildRawTransaction, buildTransaction, sign) +import CardanoClient (buildRawTransaction) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) -import Hydra.Chain (maximumNumberOfParties) -import Hydra.Chain.CardanoClient (QueryPoint (..), queryUTxOFor) -import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (Faucet), availableInitialFunds) import Hydra.Cluster.Util (keysFor) -import Hydra.Ledger (balance) -import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer) -import Test.QuickCheck (choose, generate, sized, vectorOf) +import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer) +import Test.QuickCheck (choose, generate, sized) networkId :: NetworkId networkId = Testnet $ NetworkMagic 42 @@ -36,11 +32,8 @@ data Dataset = Dataset instance Arbitrary Dataset where arbitrary = sized $ \n -> do - faucetSk <- genSigningKey - let nClients = max 1 (min maximumNumberOfParties (n `div` 10)) - clientKeys <- vectorOf nClients arbitrary - fundingTransaction <- makeGenesisFundingTx faucetSk clientKeys - genDatasetConstantUTxO clientKeys n fundingTransaction generateOneSelfTransfer + sk <- genSigningKey + genDatasetConstantUTxO sk (n `div` 10) n data ClientKeys = ClientKeys { signingKey :: SigningKey PaymentKey @@ -94,48 +87,23 @@ generateConstantUTxODataset :: IO Dataset generateConstantUTxODataset nClients nTxs = do (_, faucetSk) <- keysFor Faucet - clientKeys <- generate $ replicateM nClients arbitrary - fundingTransaction <- generate $ makeGenesisFundingTx faucetSk clientKeys - generate $ genDatasetConstantUTxO clientKeys nTxs fundingTransaction generateOneRandomTransfer + generate $ genDatasetConstantUTxO faucetSk nClients nTxs genDatasetConstantUTxO :: - -- | Clients - [ClientKeys] -> + -- | The faucet signing key + SigningKey PaymentKey -> + -- | Number of clients + Int -> -- | Number of transactions Int -> - Tx -> - (NetworkId -> (UTxO, SigningKey PaymentKey, [Tx]) -> Int -> Gen (UTxO, SigningKey PaymentKey, [Tx])) -> Gen Dataset -genDatasetConstantUTxO allClientKeys nTxs fundingTransaction tfr = do - clientDatasets <- forM allClientKeys generateClientDataset - pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} - where - generateClientDataset clientKeys@ClientKeys{externalSigningKey} = do - let vk = getVerificationKey externalSigningKey - -- NOTE: The initialUTxO must all UTXO we will later commit. We assume - -- that everything owned by the externalSigningKey will get committed - -- into the head. - initialUTxO = - utxoProducedByTx fundingTransaction - & UTxO.filter ((== mkVkAddress networkId vk) . txOutAddress) - txSequence <- - reverse - . thrd - <$> foldM (tfr networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] - pure ClientDataset{clientKeys, initialUTxO, txSequence} - - thrd (_, _, c) = c - --- Prepare funding transaction which will give every client's --- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get --- funded in the beginning of the benchmark run. -makeGenesisFundingTx :: SigningKey PaymentKey -> [ClientKeys] -> Gen Tx -makeGenesisFundingTx faucetSk clientKeys = do - let nClients = length clientKeys +genDatasetConstantUTxO faucetSk nClients nTxs = do + clientKeys <- replicateM nClients arbitrary + -- Prepare funding transaction which will give every client's + -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get + -- funded in the beginning of the benchmark run. clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) - -- NOTE: It seems if this becomes choose (1,1) we get funny signingKeys; - -- i.e. like "0001010100010001000000010100000001010001000101000000010101010001". pure (getVerificationKey externalSigningKey, amount) let fundingTransaction = buildRawTransaction @@ -144,45 +112,26 @@ makeGenesisFundingTx faucetSk clientKeys = do faucetSk (Coin availableInitialFunds) clientFunds - pure fundingTransaction + clientDatasets <- forM clientKeys (generateClientDataset fundingTransaction) + pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where initialInput = genesisUTxOPseudoTxIn networkId (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) -genDatasetConstantUTxODemo :: - -- | Clients - [ClientKeys] -> - -- | Number of transactions - Int -> - NetworkId -> - SocketPath -> - IO Dataset -genDatasetConstantUTxODemo allClientKeys nTxs networkId' nodeSocket = do - (faucetVk, faucetSk) <- keysFor Faucet - let nClients = length allClientKeys - faucetUTxO <- queryUTxOFor networkId nodeSocket QueryTip faucetVk - let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) - -- Prepare funding transaction which will give every client's - -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get - -- funded in the beginning of the benchmark run. - clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do - amount <- Coin <$> generate (choose (1, fundsAvailable `div` fromIntegral nClients)) - pure (getVerificationKey externalSigningKey, amount) + generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do + let vk = getVerificationKey externalSigningKey + -- NOTE: The initialUTxO must all UTXO we will later commit. We assume + -- that everything owned by the externalSigningKey will get committed + -- into the head. + initialUTxO = + utxoProducedByTx fundingTransaction + & UTxO.filter ((== mkVkAddress networkId vk) . txOutAddress) + txSequence <- + reverse + . thrd + <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] + pure ClientDataset{clientKeys, initialUTxO, txSequence} - let recipientOutputs = - flip map clientFunds $ \(vk, ll) -> - TxOut - (mkVkAddress networkId' vk) - (lovelaceToValue ll) - TxOutDatumNone - ReferenceScriptNone - let changeAddress = mkVkAddress networkId' faucetVk - fundingTransaction <- - buildTransaction networkId' nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case - Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} - Right body -> do - let signedTx = sign faucetSk body - pure signedTx - generate $ genDatasetConstantUTxO allClientKeys nTxs fundingTransaction generateOneSelfTransfer + thrd (_, _, c) = c diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index 0e3989173c6..f6418177816 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -14,17 +14,22 @@ import Hydra.Generator ( ClientDataset (..), Dataset (..), genDatasetConstantUTxO, - makeGenesisFundingTx, ) import Hydra.Ledger (ChainSlot (ChainSlot), applyTransactions) -import Hydra.Ledger.Cardano (Tx, cardanoLedger, generateOneSelfTransfer) +import Hydra.Ledger.Cardano (Tx, cardanoLedger) import Hydra.Ledger.Cardano.Configuration ( Globals, LedgerEnv, newLedgerEnv, ) import Test.Aeson.GenericSpecs (roundtripSpecs) -import Test.QuickCheck (Positive (Positive), Property, conjoin, counterexample, forAll, idempotentIOProperty) +import Test.QuickCheck ( + Positive (Positive), + Property, + counterexample, + forAll, + idempotentIOProperty, + ) spec :: Spec spec = parallel $ do @@ -33,25 +38,21 @@ spec = parallel $ do prop_keepsUTxOConstant :: Property prop_keepsUTxOConstant = - forAll arbitrary $ \(Positive n, clientKeys) -> do + forAll arbitrary $ \(Positive n) -> do idempotentIOProperty $ do faucetSk <- snd <$> keysFor Faucet let ledgerEnv = newLedgerEnv defaultPParams - -- XXX: non-exhaustive pattern match pure $ - forAll (makeGenesisFundingTx faucetSk clientKeys) $ \fundingTransaction -> do - Dataset{clientDatasets} <- genDatasetConstantUTxO clientKeys n fundingTransaction generateOneSelfTransfer - allProperties <- forM clientDatasets $ \ClientDataset{txSequence} -> do + forAll (genDatasetConstantUTxO faucetSk 1 n) $ + \Dataset{fundingTransaction, clientDatasets = [ClientDataset{txSequence}]} -> let initialUTxO = utxoFromTx fundingTransaction finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence - pure $ - length finalUTxO == length initialUTxO - & counterexample ("transactions: " <> prettyJSONString txSequence) - & counterexample ("utxo: " <> prettyJSONString initialUTxO) - & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) - pure $ conjoin allProperties + in length finalUTxO == length initialUTxO + & counterexample ("transactions: " <> prettyJSONString txSequence) + & counterexample ("utxo: " <> prettyJSONString initialUTxO) + & counterexample ("funding tx: " <> prettyJSONString fundingTransaction) apply :: Globals -> LedgerEnv LedgerEra -> UTxO -> Tx -> UTxO apply globals ledgerEnv utxo tx = From 4d1b2cb178443b181bd19b45bb3f1385aaeff114 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 20 Aug 2024 21:22:48 +0200 Subject: [PATCH 059/115] Gen demo datasets --- .github/workflows/network-test.yaml | 10 ++-- hydra-cluster/bench/Bench/Options.hs | 23 ++++++++ hydra-cluster/bench/Main.hs | 9 +++- hydra-cluster/src/Hydra/Generator.hs | 80 ++++++++++++++++++++++++---- 4 files changed, 107 insertions(+), 15 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 2aaf90d63c4..1d5c41c963f 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -91,13 +91,17 @@ jobs: .github/workflows/network/watch_logs.sh - # FIXME! generate dataset - touch datasets/3-nodes-demo.json + # TODO: make this a flake output / easier accesible + mkdir $(pwd)/datasets && nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ + demo-datasets \ + --output-directory=$(pwd)/datasets \ + --scaling-factor=10 \ + --node-socket=demo/devnet/node.socket # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ - datasets/3-nodes-demo.json \ + $(pwd)/datasets/demo-dataset.json \ --output-directory=$(pwd)/benchmarks \ --timeout=1000s \ --node-socket=demo/devnet/node.socket \ diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index 379828f9c7c..8c180e91027 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -32,6 +32,11 @@ data Options , nodeSocket :: SocketPath , hydraClients :: [Host] } + | DemoDatasetOptions + { outputDirectory :: Maybe FilePath + , scalingFactor :: Int + , nodeSocket :: SocketPath + } benchOptionsParser :: ParserInfo Options benchOptionsParser = @@ -40,6 +45,7 @@ benchOptionsParser = ( command "single" standaloneOptionsInfo <> command "datasets" datasetOptionsInfo <> command "demo" demoOptionsInfo + <> command "demo-datasets" demoDatasetOptionsInfo ) <**> helper ) @@ -159,6 +165,16 @@ demoOptionsInfo = \ * three hydra nodes listening on ports 4001, 4002 and 4003." ) +demoDatasetOptionsInfo :: ParserInfo Options +demoDatasetOptionsInfo = + info + demoDatasetOptionsParser + ( progDesc + "Generate demo bench dataset. \ + \ This requires having cardano node running in the background \ + \ on specified node-socket." + ) + demoOptionsParser :: Parser Options demoOptionsParser = DemoOptions @@ -168,6 +184,13 @@ demoOptionsParser = <*> nodeSocketParser <*> many hydraClientsParser +demoDatasetOptionsParser :: Parser Options +demoDatasetOptionsParser = + DemoDatasetOptions + <$> optional outputDirectoryParser + <*> scalingFactorParser + <*> nodeSocketParser + hydraClientsParser :: Parser Host hydraClientsParser = option (maybeReader readHost) $ diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 403a18970fb..652148ed51f 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -10,7 +10,7 @@ import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) import Hydra.Cluster.Fixture (defaultNetworkId) -import Hydra.Generator (Dataset (..), generateConstantUTxODataset) +import Hydra.Generator (Dataset (..), generateConstantUTxODataset, generateDemoUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) @@ -39,6 +39,13 @@ main = do DemoOptions{datasetFiles, outputDirectory, timeoutSeconds, nodeSocket, hydraClients} -> do let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraClients run outputDirectory datasetFiles action + DemoDatasetOptions{outputDirectory, scalingFactor, nodeSocket} -> do + workDir <- createSystemTempDirectory "demo-bench" + putStrLn $ "Generating single dataset in work directory: " <> workDir + numberOfTxs <- generate $ scale (* scalingFactor) getSize + dataset <- generateDemoUTxODataset numberOfTxs nodeSocket + let datasetPath = fromMaybe workDir outputDirectory "demo-dataset.json" + saveDataset datasetPath dataset where play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do putStrLn $ "Generating single dataset in work directory: " <> workDir diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 45662642add..a2ec954ee2d 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,13 +5,15 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (buildRawTransaction) +import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryUTxOFor, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) -import Hydra.Cluster.Fixture (Actor (Faucet), availableInitialFunds) +import Hydra.Cluster.Faucet (FaucetException (..)) +import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds, defaultNetworkId) import Hydra.Cluster.Util (keysFor) -import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer) +import Hydra.Ledger (balance) +import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer) import Test.QuickCheck (choose, generate, sized) networkId :: NetworkId @@ -121,17 +123,73 @@ genDatasetConstantUTxO faucetSk nClients nTxs = do (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do - let vk = getVerificationKey externalSigningKey - -- NOTE: The initialUTxO must all UTXO we will later commit. We assume - -- that everything owned by the externalSigningKey will get committed - -- into the head. - initialUTxO = - utxoProducedByTx fundingTransaction - & UTxO.filter ((== mkVkAddress networkId vk) . txOutAddress) + let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction txSequence <- reverse . thrd <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] pure ClientDataset{clientKeys, initialUTxO, txSequence} - thrd (_, _, c) = c +generateDemoUTxODataset :: + -- | Number of transactions + Int -> + SocketPath -> + IO Dataset +generateDemoUTxODataset nTxs nodeSocket = do + (faucetVk, faucetSk) <- keysFor Faucet + clientKeys <- do + let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] + let toClientKeys (actor, actorFunds) = do + sk <- snd <$> keysFor actor + fundsSk <- snd <$> keysFor actorFunds + pure $ ClientKeys sk fundsSk + forM actors toClientKeys + let nClients = length clientKeys + + faucetUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip faucetVk + let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) + -- Prepare funding transaction which will give every client's + -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get + -- funded in the beginning of the demo benchmark run. + clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do + amount <- Coin <$> generate (choose (1, fundsAvailable `div` fromIntegral nClients)) + pure (getVerificationKey externalSigningKey, amount) + let recipientOutputs = + flip map clientFunds $ \(vk, ll) -> + TxOut + (mkVkAddress defaultNetworkId vk) + (lovelaceToValue ll) + TxOutDatumNone + ReferenceScriptNone + let changeAddress = mkVkAddress defaultNetworkId faucetVk + fundingTransaction <- + buildTransaction defaultNetworkId nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case + Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} + Right body -> do + let signedTx = sign faucetSk body + pure signedTx + + generate $ do + clientDatasets <- forM clientKeys (generateClientDemoDataset fundingTransaction) + pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} + where + generateClientDemoDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do + let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction + txSequence <- + reverse + . thrd + <$> foldM (generateOneSelfTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] + pure ClientDataset{clientKeys, initialUTxO, txSequence} + +-- * Helpers +thrd :: (a, b, c) -> c +thrd (_, _, c) = c + +withInitialUTxO :: SigningKey PaymentKey -> Tx -> UTxO +withInitialUTxO externalSigningKey fundingTransaction = + let vk = getVerificationKey externalSigningKey + in -- NOTE: The initialUTxO must all UTXO we will later commit. We assume + -- that everything owned by the externalSigningKey will get committed + -- into the head. + utxoProducedByTx fundingTransaction + & UTxO.filter ((== mkVkAddress networkId vk) . txOutAddress) From eaef6ecf71987d6539e1de09ee065e19b1e298a3 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 13:44:38 +0200 Subject: [PATCH 060/115] Refactor HydraClient to keep peer information This is useful to triger node actions not depending on localhost, like in demo-bench where commit has to be done on running peers. --- hydra-cluster/src/HydraNode.hs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index fde3340f94a..1bbb15bf277 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -48,6 +48,7 @@ import Prelude qualified data HydraClient = HydraClient { hydraNodeId :: Int + , peer :: Host , connection :: Connection , tracer :: Tracer IO HydraNodeLog } @@ -172,22 +173,22 @@ waitForAll tracer delay nodes expected = do -- | Helper to make it easy to obtain a commit tx using some wallet utxo. -- Create a commit tx using the hydra-node for later submission. requestCommitTx :: HydraClient -> UTxO -> IO Tx -requestCommitTx HydraClient{hydraNodeId} utxos = +requestCommitTx HydraClient{peer = Host{hostname, port}} utxos = runReq defaultHttpConfig request <&> commitTx . responseBody where request = Req.req POST - (Req.http "127.0.0.1" /: "commit") + (Req.http hostname /: "commit") (ReqBodyJson $ SimpleCommitRequest @Tx utxos) (Proxy :: Proxy (JsonResponse (DraftCommitTxResponse Tx))) - (Req.port $ 4_000 + hydraNodeId) + (Req.port (fromInteger . toInteger $ port)) -- | Submit a decommit transaction to the hydra-node. postDecommit :: HydraClient -> Tx -> IO () -postDecommit HydraClient{hydraNodeId} decommitTx = do +postDecommit HydraClient{peer = Host{hostname, port}} decommitTx = do void $ - parseUrlThrow ("POST http://127.0.0.1:" <> show (4000 + hydraNodeId) <> "/decommit") + parseUrlThrow ("POST http://" <> T.unpack hostname <> ":" <> show port <> "/decommit") <&> setRequestBodyJSON decommitTx >>= httpLbs @@ -195,19 +196,19 @@ postDecommit HydraClient{hydraNodeId} decommitTx = do -- avoid parsing responses using the same data types as the system under test, -- this parses the response as a 'UTxO' type as we often need to pick it apart. getSnapshotUTxO :: HydraClient -> IO UTxO -getSnapshotUTxO HydraClient{hydraNodeId} = +getSnapshotUTxO HydraClient{peer = Host{hostname, port}} = runReq defaultHttpConfig request <&> responseBody where request = Req.req GET - (Req.http "127.0.0.1" /: "snapshot" /: "utxo") + (Req.http hostname /: "snapshot" /: "utxo") NoReqBody (Proxy :: Proxy (JsonResponse UTxO)) - (Req.port $ 4_000 + hydraNodeId) + (Req.port (fromInteger . toInteger $ port)) getMetrics :: HasCallStack => HydraClient -> IO ByteString -getMetrics HydraClient{hydraNodeId} = do +getMetrics HydraClient{hydraNodeId, peer = Host{hostname}} = do failAfter 3 $ try (runReq defaultHttpConfig request) >>= \case Left (e :: HttpException) -> failure $ "Request for hydra-node metrics failed: " <> show e @@ -216,7 +217,7 @@ getMetrics HydraClient{hydraNodeId} = do request = Req.req GET - (Req.http "127.0.0.1" /: "metrics") + (Req.http hostname /: "metrics") NoReqBody Req.bsResponse (Req.port $ 6_000 + hydraNodeId) @@ -409,7 +410,7 @@ withConnectionToNode tracer hydraNodeId = port = fromInteger $ 4_000 + toInteger hydraNodeId withConnectionToNodeHost :: forall a. Tracer IO HydraNodeLog -> Int -> Host -> Bool -> (HydraClient -> IO a) -> IO a -withConnectionToNodeHost tracer hydraNodeId Host{hostname, port} showHistory action = do +withConnectionToNodeHost tracer hydraNodeId peer@Host{hostname, port} showHistory action = do connectedOnce <- newIORef False tryConnect connectedOnce (200 :: Int) where @@ -432,7 +433,7 @@ withConnectionToNodeHost tracer hydraNodeId Host{hostname, port} showHistory act doConnect connectedOnce = runClient (T.unpack hostname) (fromInteger . toInteger $ port) historyMode $ \connection -> do atomicWriteIORef connectedOnce True traceWith tracer (NodeStarted hydraNodeId) - res <- action $ HydraClient{hydraNodeId, connection, tracer} + res <- action $ HydraClient{hydraNodeId, peer, connection, tracer} sendClose connection ("Bye" :: Text) pure res From b29df8f7cbf675354401db2047849914e42bdb81 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 13:45:28 +0200 Subject: [PATCH 061/115] dbg --- hydra-cluster/src/Hydra/Generator.hs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index a2ec954ee2d..b088a0a7f2f 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -9,6 +9,7 @@ import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransacti import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) +import Hydra.Cardano.Api.Pretty (renderTx) import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds, defaultNetworkId) import Hydra.Cluster.Util (keysFor) @@ -147,13 +148,19 @@ generateDemoUTxODataset nTxs nodeSocket = do let nClients = length clientKeys faucetUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip faucetVk + putStrLn $ "faucetUTxO: " <> renderUTxO faucetUTxO let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) + putStrLn $ "fundsAvailable: " <> show fundsAvailable -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the demo benchmark run. clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do amount <- Coin <$> generate (choose (1, fundsAvailable `div` fromIntegral nClients)) pure (getVerificationKey externalSigningKey, amount) + + let clientFundsDbg = bimap (mkVkAddress @Era defaultNetworkId) lovelaceToValue <$> clientFunds + putStrLn $ "clientFunds: " <> show clientFundsDbg + let recipientOutputs = flip map clientFunds $ \(vk, ll) -> TxOut @@ -168,7 +175,7 @@ generateDemoUTxODataset nTxs nodeSocket = do Right body -> do let signedTx = sign faucetSk body pure signedTx - + putStrLn $ "fundingTransaction: " <> renderTx fundingTransaction generate $ do clientDatasets <- forM clientKeys (generateClientDemoDataset fundingTransaction) pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} From 7be87ce111010d3215793dc76634f5035a82bd83 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 14:13:02 +0200 Subject: [PATCH 062/115] Try to use the pre-seeded client funds instead of faucet's --- hydra-cluster/src/Hydra/Generator.hs | 32 +++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index b088a0a7f2f..a8053ac758b 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -145,32 +145,30 @@ generateDemoUTxODataset nTxs nodeSocket = do fundsSk <- snd <$> keysFor actorFunds pure $ ClientKeys sk fundsSk forM actors toClientKeys - let nClients = length clientKeys - faucetUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip faucetVk - putStrLn $ "faucetUTxO: " <> renderUTxO faucetUTxO - let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) - putStrLn $ "fundsAvailable: " <> show fundsAvailable - -- Prepare funding transaction which will give every client's - -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get - -- funded in the beginning of the demo benchmark run. - clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do - amount <- Coin <$> generate (choose (1, fundsAvailable `div` fromIntegral nClients)) - pure (getVerificationKey externalSigningKey, amount) - - let clientFundsDbg = bimap (mkVkAddress @Era defaultNetworkId) lovelaceToValue <$> clientFunds - putStrLn $ "clientFunds: " <> show clientFundsDbg + clientFunds <- forM clientKeys $ \ClientKeys{signingKey} -> do + let address = mkVkAddress @Era defaultNetworkId (getVerificationKey signingKey) + putStrLn $ "client: " <> show address + clientUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip faucetVk + putStrLn $ "client UTxO: " <> renderUTxO clientUTxO + let fundsAvailable = selectLovelace (balance @Tx clientUTxO) + putStrLn $ "client funds available: " <> show fundsAvailable + pure (address, fundsAvailable, clientUTxO) let recipientOutputs = - flip map clientFunds $ \(vk, ll) -> + flip map clientFunds $ \(addr, ll, _) -> TxOut - (mkVkAddress defaultNetworkId vk) + addr (lovelaceToValue ll) TxOutDatumNone ReferenceScriptNone + + -- REVIEW let changeAddress = mkVkAddress defaultNetworkId faucetVk + + let clientsUTxO = foldMap thrd clientFunds fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case + buildTransaction defaultNetworkId nodeSocket changeAddress clientsUTxO [] recipientOutputs >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign faucetSk body From 6c517b7418db7b45aa9a49bb89399398e2017927 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 14:28:54 +0200 Subject: [PATCH 063/115] Generate each client dataset from a different client funding tx Instead of usign a single one from the faucet current utxo. --- hydra-cluster/src/Hydra/Generator.hs | 37 ++++++++++++---------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index a8053ac758b..426dadcb844 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -14,7 +14,7 @@ import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds, defaultNetworkId) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) -import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer) +import Hydra.Ledger.Cardano (emptyTxBody, genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer, unsafeBuildTransaction) import Test.QuickCheck (choose, generate, sized) networkId :: NetworkId @@ -146,37 +146,32 @@ generateDemoUTxODataset nTxs nodeSocket = do pure $ ClientKeys sk fundsSk forM actors toClientKeys - clientFunds <- forM clientKeys $ \ClientKeys{signingKey} -> do + clientFundingTxs <- forM clientKeys $ \clientKey@ClientKeys{signingKey} -> do let address = mkVkAddress @Era defaultNetworkId (getVerificationKey signingKey) putStrLn $ "client: " <> show address clientUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip faucetVk putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable - pure (address, fundsAvailable, clientUTxO) - - let recipientOutputs = - flip map clientFunds $ \(addr, ll, _) -> + let recipientOutputs = TxOut - addr - (lovelaceToValue ll) + address + (lovelaceToValue fundsAvailable) TxOutDatumNone ReferenceScriptNone + fundingTransaction <- + buildTransaction defaultNetworkId nodeSocket address clientUTxO [] [recipientOutputs] >>= \case + Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} + Right body -> do + let signedTx = sign faucetSk body + pure signedTx + putStrLn $ "fundingTransaction: " <> renderTx fundingTransaction + pure (clientKey, fundingTransaction) - -- REVIEW - let changeAddress = mkVkAddress defaultNetworkId faucetVk - - let clientsUTxO = foldMap thrd clientFunds - fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress clientsUTxO [] recipientOutputs >>= \case - Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} - Right body -> do - let signedTx = sign faucetSk body - pure signedTx - putStrLn $ "fundingTransaction: " <> renderTx fundingTransaction generate $ do - clientDatasets <- forM clientKeys (generateClientDemoDataset fundingTransaction) - pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} + clientDatasets <- forM clientFundingTxs (\(clientKey, fundingTransaction) -> generateClientDemoDataset fundingTransaction clientKey) + let emptyTx = unsafeBuildTransaction emptyTxBody + pure Dataset{fundingTransaction = emptyTx, clientDatasets, title = Nothing, description = Nothing} where generateClientDemoDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction From 5a613080d3e7935c372207f6ddd9a6c1a31dbcf4 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 14:39:42 +0200 Subject: [PATCH 064/115] Try not to specify any output --- hydra-cluster/src/Hydra/Generator.hs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 426dadcb844..6b008992198 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -153,17 +153,15 @@ generateDemoUTxODataset nTxs nodeSocket = do putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable - let recipientOutputs = - TxOut - address - (lovelaceToValue fundsAvailable) - TxOutDatumNone - ReferenceScriptNone + -- Here we specify no outputs in the transaction so that a change output with the + -- entire value is created and paid to the change address. + let collateralTxIns = mempty + let changeAddress = address fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket address clientUTxO [] [recipientOutputs] >>= \case + buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do - let signedTx = sign faucetSk body + let signedTx = sign signingKey body pure signedTx putStrLn $ "fundingTransaction: " <> renderTx fundingTransaction pure (clientKey, fundingTransaction) From 2a69816e11f55bc9040a5f7d595ee9c32f58620f Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 14:42:18 +0200 Subject: [PATCH 065/115] Try using mkTxOutAutoBalance instead --- hydra-cluster/src/Hydra/Generator.hs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 6b008992198..f621239bd75 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,7 +5,7 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryUTxOFor, sign) +import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryProtocolParameters, queryUTxOFor, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) @@ -146,6 +146,7 @@ generateDemoUTxODataset nTxs nodeSocket = do pure $ ClientKeys sk fundsSk forM actors toClientKeys + pparams <- queryProtocolParameters defaultNetworkId nodeSocket QueryTip clientFundingTxs <- forM clientKeys $ \clientKey@ClientKeys{signingKey} -> do let address = mkVkAddress @Era defaultNetworkId (getVerificationKey signingKey) putStrLn $ "client: " <> show address @@ -153,12 +154,17 @@ generateDemoUTxODataset nTxs nodeSocket = do putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable - -- Here we specify no outputs in the transaction so that a change output with the - -- entire value is created and paid to the change address. + let recipientOutputs = + mkTxOutAutoBalance + pparams + address + (lovelaceToValue fundsAvailable) + TxOutDatumNone + ReferenceScriptNone let collateralTxIns = mempty let changeAddress = address fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case + buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [recipientOutputs] >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign signingKey body From 3a67f0c5907e0ac87e83bb535720e44986a42c7c Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 14:49:16 +0200 Subject: [PATCH 066/115] Fix client utxo --- hydra-cluster/src/Hydra/Generator.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index f621239bd75..a707c260b9e 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -137,7 +137,6 @@ generateDemoUTxODataset :: SocketPath -> IO Dataset generateDemoUTxODataset nTxs nodeSocket = do - (faucetVk, faucetSk) <- keysFor Faucet clientKeys <- do let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] let toClientKeys (actor, actorFunds) = do @@ -148,9 +147,10 @@ generateDemoUTxODataset nTxs nodeSocket = do pparams <- queryProtocolParameters defaultNetworkId nodeSocket QueryTip clientFundingTxs <- forM clientKeys $ \clientKey@ClientKeys{signingKey} -> do - let address = mkVkAddress @Era defaultNetworkId (getVerificationKey signingKey) + let clientVk = getVerificationKey signingKey + let address = mkVkAddress @Era defaultNetworkId clientVk putStrLn $ "client: " <> show address - clientUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip faucetVk + clientUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip clientVk putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable From 6eec71593bb2da6bcec2fa15af312d713c853bfd Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 14:51:19 +0200 Subject: [PATCH 067/115] Try not to specify any output ~ v2 --- hydra-cluster/src/Hydra/Generator.hs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index a707c260b9e..34173faf448 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -154,17 +154,12 @@ generateDemoUTxODataset nTxs nodeSocket = do putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable - let recipientOutputs = - mkTxOutAutoBalance - pparams - address - (lovelaceToValue fundsAvailable) - TxOutDatumNone - ReferenceScriptNone + -- Here we specify no outputs in the transaction so that a change output with the + -- entire value is created and paid to the change address. let collateralTxIns = mempty let changeAddress = address fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [recipientOutputs] >>= \case + buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign signingKey body From b8bfb3e560ee92445196c1c669a0ab30acd11d2e Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 15:06:21 +0200 Subject: [PATCH 068/115] Go back to unbalanced outputs ~ v2 --- hydra-cluster/src/Hydra/Generator.hs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 34173faf448..319998aaa5d 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,7 +5,7 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryProtocolParameters, queryUTxOFor, sign) +import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryUTxOFor, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) @@ -145,7 +145,6 @@ generateDemoUTxODataset nTxs nodeSocket = do pure $ ClientKeys sk fundsSk forM actors toClientKeys - pparams <- queryProtocolParameters defaultNetworkId nodeSocket QueryTip clientFundingTxs <- forM clientKeys $ \clientKey@ClientKeys{signingKey} -> do let clientVk = getVerificationKey signingKey let address = mkVkAddress @Era defaultNetworkId clientVk @@ -154,12 +153,16 @@ generateDemoUTxODataset nTxs nodeSocket = do putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable - -- Here we specify no outputs in the transaction so that a change output with the - -- entire value is created and paid to the change address. + let recipientOutputs = + TxOut + address + (lovelaceToValue fundsAvailable) + TxOutDatumNone + ReferenceScriptNone let collateralTxIns = mempty let changeAddress = address fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case + buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [recipientOutputs] >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign signingKey body From 99035a4141bc3cff634391da1d2c51ac0ed5a97e Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 15:10:08 +0200 Subject: [PATCH 069/115] Make funding tx optional for datasets This is used to seed the network in other scenarios. This one assumes the cluster is pre-seeded. --- hydra-cluster/bench/Bench/EndToEnd.hs | 5 +++-- hydra-cluster/src/Hydra/Generator.hs | 9 ++++----- hydra-cluster/test/Test/GeneratorSpec.hs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 145a32dd99c..c75a47749ed 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -297,8 +297,9 @@ seedNetwork node@RunningNode{nodeSocket, networkId} Dataset{fundingTransaction, where fundClients = do putTextLn "Fund scenario from faucet" - submitTransaction networkId nodeSocket fundingTransaction - void $ awaitTransaction networkId nodeSocket fundingTransaction + let fundingTx = fromMaybe (error "missing fundingTransaction") fundingTransaction + submitTransaction networkId nodeSocket fundingTx + void $ awaitTransaction networkId nodeSocket fundingTx fuelWith100Ada ClientKeys{signingKey} = do let vk = getVerificationKey signingKey diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 319998aaa5d..147db658608 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -14,7 +14,7 @@ import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds, defaultNetworkId) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) -import Hydra.Ledger.Cardano (emptyTxBody, genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer, unsafeBuildTransaction) +import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer) import Test.QuickCheck (choose, generate, sized) networkId :: NetworkId @@ -25,7 +25,7 @@ networkId = Testnet $ NetworkMagic 42 -- against one or more `HydraNode`s. A dataset can optionally have a `title` and `description` -- which will be used to report results. data Dataset = Dataset - { fundingTransaction :: Tx + { fundingTransaction :: Maybe Tx , clientDatasets :: [ClientDataset] , title :: Maybe Text , description :: Maybe Text @@ -116,7 +116,7 @@ genDatasetConstantUTxO faucetSk nClients nTxs = do (Coin availableInitialFunds) clientFunds clientDatasets <- forM clientKeys (generateClientDataset fundingTransaction) - pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} + pure Dataset{fundingTransaction = Just fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where initialInput = genesisUTxOPseudoTxIn @@ -172,8 +172,7 @@ generateDemoUTxODataset nTxs nodeSocket = do generate $ do clientDatasets <- forM clientFundingTxs (\(clientKey, fundingTransaction) -> generateClientDemoDataset fundingTransaction clientKey) - let emptyTx = unsafeBuildTransaction emptyTxBody - pure Dataset{fundingTransaction = emptyTx, clientDatasets, title = Nothing, description = Nothing} + pure Dataset{fundingTransaction = Nothing, clientDatasets, title = Nothing, description = Nothing} where generateClientDemoDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index f6418177816..fd10e174445 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -46,7 +46,7 @@ prop_keepsUTxOConstant = -- XXX: non-exhaustive pattern match pure $ forAll (genDatasetConstantUTxO faucetSk 1 n) $ - \Dataset{fundingTransaction, clientDatasets = [ClientDataset{txSequence}]} -> + \Dataset{fundingTransaction = Just fundingTransaction, clientDatasets = [ClientDataset{txSequence}]} -> let initialUTxO = utxoFromTx fundingTransaction finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence in length finalUTxO == length initialUTxO From 374147a1c29662e48849db0a7181bc6db5ae1fa4 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 15:17:22 +0200 Subject: [PATCH 070/115] Try not to specify any output ~ v3 --- hydra-cluster/src/Hydra/Generator.hs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 147db658608..0d723badb8c 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -153,16 +153,12 @@ generateDemoUTxODataset nTxs nodeSocket = do putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable - let recipientOutputs = - TxOut - address - (lovelaceToValue fundsAvailable) - TxOutDatumNone - ReferenceScriptNone let collateralTxIns = mempty let changeAddress = address + -- Here we specify no outputs in the transaction so that a change output with the + -- entire value is created and paid to the change address. fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [recipientOutputs] >>= \case + buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign signingKey body From 02503406cf5ac8f2e7d65859d8e72b38af0a3943 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 21 Aug 2024 15:18:24 +0200 Subject: [PATCH 071/115] Try using mkTxOutAutoBalance instead ~ v2 --- hydra-cluster/src/Hydra/Generator.hs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 0d723badb8c..6c4770addde 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,7 +5,7 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryUTxOFor, sign) +import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryProtocolParameters, queryUTxOFor, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) @@ -145,6 +145,7 @@ generateDemoUTxODataset nTxs nodeSocket = do pure $ ClientKeys sk fundsSk forM actors toClientKeys + pparams <- queryProtocolParameters defaultNetworkId nodeSocket QueryTip clientFundingTxs <- forM clientKeys $ \clientKey@ClientKeys{signingKey} -> do let clientVk = getVerificationKey signingKey let address = mkVkAddress @Era defaultNetworkId clientVk @@ -155,10 +156,15 @@ generateDemoUTxODataset nTxs nodeSocket = do putStrLn $ "client funds available: " <> show fundsAvailable let collateralTxIns = mempty let changeAddress = address - -- Here we specify no outputs in the transaction so that a change output with the - -- entire value is created and paid to the change address. + let recipientOutputs = + mkTxOutAutoBalance + pparams + address + (lovelaceToValue fundsAvailable) + TxOutDatumNone + ReferenceScriptNone fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case + buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [recipientOutputs] >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign signingKey body From 561725428a494c80770d002b132d17980b489ebe Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 21 Aug 2024 17:03:58 +0100 Subject: [PATCH 072/115] WIP; Revert faucet; use buildRawTransaction --- hydra-cluster/src/Hydra/Cluster/Faucet.hs | 31 ++++++----------- hydra-cluster/src/Hydra/Generator.hs | 41 ++++++++++++++--------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/hydra-cluster/src/Hydra/Cluster/Faucet.hs b/hydra-cluster/src/Hydra/Cluster/Faucet.hs index 59c20e4a96c..4d62dee0e4e 100644 --- a/hydra-cluster/src/Hydra/Cluster/Faucet.hs +++ b/hydra-cluster/src/Hydra/Cluster/Faucet.hs @@ -106,30 +106,19 @@ returnFundsToFaucet :: RunningNode -> Actor -> IO () -returnFundsToFaucet tracer node sender = do - senderKeys <- keysFor sender - returnAmount <- returnFundsToFaucet' tracer node (snd senderKeys) - traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount} - -returnFundsToFaucet' :: - Tracer IO FaucetLog -> - RunningNode -> - SigningKey PaymentKey -> - IO Coin -returnFundsToFaucet' tracer RunningNode{networkId, nodeSocket} senderSk = do +returnFundsToFaucet tracer RunningNode{networkId, nodeSocket} sender = do (faucetVk, _) <- keysFor Faucet let faucetAddress = mkVkAddress networkId faucetVk - let senderVk = getVerificationKey senderSk + + (senderVk, senderSk) <- keysFor sender utxo <- queryUTxOFor networkId nodeSocket QueryTip senderVk - if null utxo - then pure 0 - else retryOnExceptions tracer $ do - let utxoValue = balance @Tx utxo - let allLovelace = selectLovelace utxoValue - tx <- sign senderSk <$> buildTxBody utxo faucetAddress - submitTransaction networkId nodeSocket tx - void $ awaitTransaction networkId nodeSocket tx - pure allLovelace + unless (null utxo) . retryOnExceptions tracer $ do + let utxoValue = balance @Tx utxo + let allLovelace = selectLovelace utxoValue + tx <- sign senderSk <$> buildTxBody utxo faucetAddress + submitTransaction networkId nodeSocket tx + void $ awaitTransaction networkId nodeSocket tx + traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount = allLovelace} where buildTxBody utxo faucetAddress = -- Here we specify no outputs in the transaction so that a change output with the diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 6c4770addde..34d6e924990 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -9,6 +9,7 @@ import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransacti import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) +import Data.List qualified as List import Hydra.Cardano.Api.Pretty (renderTx) import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds, defaultNetworkId) @@ -145,7 +146,7 @@ generateDemoUTxODataset nTxs nodeSocket = do pure $ ClientKeys sk fundsSk forM actors toClientKeys - pparams <- queryProtocolParameters defaultNetworkId nodeSocket QueryTip + -- pparams <- queryProtocolParameters defaultNetworkId nodeSocket QueryTip clientFundingTxs <- forM clientKeys $ \clientKey@ClientKeys{signingKey} -> do let clientVk = getVerificationKey signingKey let address = mkVkAddress @Era defaultNetworkId clientVk @@ -154,21 +155,29 @@ generateDemoUTxODataset nTxs nodeSocket = do putStrLn $ "client UTxO: " <> renderUTxO clientUTxO let fundsAvailable = selectLovelace (balance @Tx clientUTxO) putStrLn $ "client funds available: " <> show fundsAvailable - let collateralTxIns = mempty - let changeAddress = address - let recipientOutputs = - mkTxOutAutoBalance - pparams - address - (lovelaceToValue fundsAvailable) - TxOutDatumNone - ReferenceScriptNone - fundingTransaction <- - buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [recipientOutputs] >>= \case - Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} - Right body -> do - let signedTx = sign signingKey body - pure signedTx + -- let collateralTxIns = mempty + -- let changeAddress = address + -- let recipientOutputs = + -- mkTxOutAutoBalance + -- pparams + -- address + -- (lovelaceToValue fundsAvailable) + -- TxOutDatumNone + -- ReferenceScriptNone + -- putStrLn $ "txOutAutoBalance: " <> show recipientOutputs + let fundingTransaction = + buildRawTransaction + defaultNetworkId + (fst . List.head . UTxO.pairs $ clientUTxO) + signingKey + fundsAvailable + [(clientVk, fundsAvailable)] + + -- buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case + -- Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} + -- Right body -> pure $ sign signingKey body + -- let signedTx = sign signingKey body + -- pure signedTx putStrLn $ "fundingTransaction: " <> renderTx fundingTransaction pure (clientKey, fundingTransaction) From ef399ad68cb1ef0c9e2965d620ec870f65136d37 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 22 Aug 2024 09:23:59 +0100 Subject: [PATCH 073/115] Create workdir if it doesn't exist --- hydra-cluster/bench/Main.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 652148ed51f..ac9fbc93d9d 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -52,6 +52,7 @@ main = do numberOfTxs <- generate $ scale (* scalingFactor) getSize dataset <- generateConstantUTxODataset (fromIntegral clusterSize) numberOfTxs let datasetPath = workDir "dataset.json" + createDirectoryIfMissing True workDir saveDataset datasetPath dataset let action = bench startingNodeId timeoutSeconds run outputDirectory [datasetPath] action From 04648a9f38c93804df64628faa5819ed9643e2f0 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 22 Aug 2024 10:08:59 +0100 Subject: [PATCH 074/115] WIP on generateDemoUTxO function; just missing witnesses. --- .github/workflows/network-test.yaml | 5 +- hydra-cluster/bench/Main.hs | 5 +- hydra-cluster/src/Hydra/Generator.hs | 69 ++++++++-------------------- 3 files changed, 25 insertions(+), 54 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 1d5c41c963f..b3466dff2bd 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -45,14 +45,15 @@ jobs: cd demo ./prepare-devnet.sh docker compose up -d cardano-node + # :tear: socket permissions. + sudo chown runner:docker devnet/node.socket + ./seed-devnet.sh # Specify two docker compose yamls; the second one overrides the # images to use the netem ones specifically docker compose -f docker-compose.yaml -f docker-compose-netem.yaml up -d hydra-node-{1,2,3} sleep 10 docker ps - # :tear: socket permissions. - sudo chown runner:docker devnet/node.socket - name: Build required nix and docker derivations run: | diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index ac9fbc93d9d..821bab155de 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -14,7 +14,7 @@ import Hydra.Generator (Dataset (..), generateConstantUTxODataset, generateDemoU import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) -import System.FilePath (takeFileName, ()) +import System.FilePath (takeDirectory, takeFileName, ()) import Test.HUnit.Lang (formatFailureReason) import Test.QuickCheck (generate, getSize, scale) @@ -45,6 +45,7 @@ main = do numberOfTxs <- generate $ scale (* scalingFactor) getSize dataset <- generateDemoUTxODataset numberOfTxs nodeSocket let datasetPath = fromMaybe workDir outputDirectory "demo-dataset.json" + createDirectoryIfMissing True workDir saveDataset datasetPath dataset where play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do @@ -52,7 +53,6 @@ main = do numberOfTxs <- generate $ scale (* scalingFactor) getSize dataset <- generateConstantUTxODataset (fromIntegral clusterSize) numberOfTxs let datasetPath = workDir "dataset.json" - createDirectoryIfMissing True workDir saveDataset datasetPath dataset let action = bench startingNodeId timeoutSeconds run outputDirectory [datasetPath] action @@ -88,6 +88,7 @@ main = do saveDataset :: FilePath -> Dataset -> IO () saveDataset f dataset = do + createDirectoryIfMissing True (takeDirectory f) putStrLn $ "Writing dataset to: " <> f encodeFile f dataset diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 34d6e924990..e9f72a52273 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -133,65 +133,34 @@ genDatasetConstantUTxO faucetSk nClients nTxs = do pure ClientDataset{clientKeys, initialUTxO, txSequence} generateDemoUTxODataset :: - -- | Number of transactions Int -> SocketPath -> IO Dataset -generateDemoUTxODataset nTxs nodeSocket = do +generateDemoUTxODataset nTxns nodeSocket = do + -- FIXME: Client keys hard-coded; could read from arguments. clientKeys <- do let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] - let toClientKeys (actor, actorFunds) = do + let toClientKeys (actor, funds) = do sk <- snd <$> keysFor actor - fundsSk <- snd <$> keysFor actorFunds + fundsSk <- snd <$> keysFor funds pure $ ClientKeys sk fundsSk forM actors toClientKeys - - -- pparams <- queryProtocolParameters defaultNetworkId nodeSocket QueryTip - clientFundingTxs <- forM clientKeys $ \clientKey@ClientKeys{signingKey} -> do - let clientVk = getVerificationKey signingKey - let address = mkVkAddress @Era defaultNetworkId clientVk - putStrLn $ "client: " <> show address - clientUTxO <- queryUTxOFor defaultNetworkId nodeSocket QueryTip clientVk - putStrLn $ "client UTxO: " <> renderUTxO clientUTxO - let fundsAvailable = selectLovelace (balance @Tx clientUTxO) - putStrLn $ "client funds available: " <> show fundsAvailable - -- let collateralTxIns = mempty - -- let changeAddress = address - -- let recipientOutputs = - -- mkTxOutAutoBalance - -- pparams - -- address - -- (lovelaceToValue fundsAvailable) - -- TxOutDatumNone - -- ReferenceScriptNone - -- putStrLn $ "txOutAutoBalance: " <> show recipientOutputs - let fundingTransaction = - buildRawTransaction - defaultNetworkId - (fst . List.head . UTxO.pairs $ clientUTxO) - signingKey - fundsAvailable - [(clientVk, fundsAvailable)] - - -- buildTransaction defaultNetworkId nodeSocket changeAddress clientUTxO collateralTxIns [] >>= \case - -- Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} - -- Right body -> pure $ sign signingKey body - -- let signedTx = sign signingKey body - -- pure signedTx - putStrLn $ "fundingTransaction: " <> renderTx fundingTransaction - pure (clientKey, fundingTransaction) - - generate $ do - clientDatasets <- forM clientFundingTxs (\(clientKey, fundingTransaction) -> generateClientDemoDataset fundingTransaction clientKey) - pure Dataset{fundingTransaction = Nothing, clientDatasets, title = Nothing, description = Nothing} + clientDatasets <- forM clientKeys generateClientDataset + pure $ Dataset{fundingTransaction = Nothing, clientDatasets = clientDatasets, title = Nothing, description = Nothing} where - generateClientDemoDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do - let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction - txSequence <- - reverse - . thrd - <$> foldM (generateOneSelfTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] - pure ClientDataset{clientKeys, initialUTxO, txSequence} + generateClientDataset :: ClientKeys -> IO ClientDataset + generateClientDataset clientKeys@ClientKeys{signingKey, externalSigningKey} = do + -- TODO: Missing witnesses. + -- bench-e2e: SubmitTxValidationError (TxValidationErrorInCardanoMode (ShelleyTxValidationError ShelleyBasedEraBabbage (Appl6ace848155a0e967af64f4d00cf8acee8adc95a6b0d"}])))) :| [])))) + let clientVk = getVerificationKey signingKey + initialUTxO <- queryUTxOFor networkId nodeSocket QueryTip clientVk + -- FIXME: Improve error + when (null initialUTxO) $ do + error "No initial UTxOs. Did you seed your devnet?" + generate $ do + txSequence <- + reverse . thrd <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, mempty) [1 .. nTxns] + pure ClientDataset{clientKeys, initialUTxO, txSequence} -- * Helpers thrd :: (a, b, c) -> c From 0d7c62c2164d05f096616f103dea21dc0d929c1e Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 22 Aug 2024 13:10:43 +0100 Subject: [PATCH 075/115] Small refactors around types --- hydra-cluster/bench/Bench/EndToEnd.hs | 14 +++++++------- hydra-cluster/bench/Main.hs | 1 - hydra-cluster/src/Hydra/Generator.hs | 8 +++----- hydra-node/src/Hydra/Ledger/Cardano.hs | 19 +++++++++---------- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index c75a47749ed..93a825db75c 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -144,7 +144,7 @@ scenario :: HydraClient -> [HydraClient] -> IO Summary -scenario hydraTracer node workDir dataset@Dataset{clientDatasets, title, description} parties leader followers = do +scenario hydraTracer node workDir Dataset{clientDatasets, title, description} parties leader followers = do let clusterSize = fromIntegral $ length clientDatasets let clients = leader : followers @@ -157,13 +157,13 @@ scenario hydraTracer node workDir dataset@Dataset{clientDatasets, title, descrip else headIsInitializingWith parties putTextLn "Comitting initialUTxO from dataset" - expectedUTxO <- commitUTxO node clients dataset + expectedUTxO <- commitUTxO node clients clientDatasets waitFor hydraTracer (fromIntegral $ 10 * clusterSize) clients $ output "HeadIsOpen" ["utxo" .= expectedUTxO, "headId" .= headId] putTextLn "HeadIsOpen" - processedTransactions <- processTransactions clients dataset + processedTransactions <- processTransactions clients clientDatasets putTextLn "Closing the Head" send leader $ input "Close" [] @@ -308,8 +308,8 @@ seedNetwork node@RunningNode{nodeSocket, networkId} Dataset{fundingTransaction, -- | Commit all (expected to exit) 'initialUTxO' from the dataset using the -- (asumed same sequence) of clients. -commitUTxO :: RunningNode -> [HydraClient] -> Dataset -> IO UTxO -commitUTxO node clients Dataset{clientDatasets} = +commitUTxO :: RunningNode -> [HydraClient] -> [ClientDataset] -> IO UTxO +commitUTxO node clients clientDatasets = mconcat <$> forM (zip clients clientDatasets) doCommit where doCommit (client, ClientDataset{initialUTxO, clientKeys = ClientKeys{externalSigningKey}}) = do @@ -318,8 +318,8 @@ commitUTxO node clients Dataset{clientDatasets} = >>= submitTx node pure initialUTxO -processTransactions :: [HydraClient] -> Dataset -> IO (Map.Map TxId Event) -processTransactions clients Dataset{clientDatasets} = do +processTransactions :: [HydraClient] -> [ClientDataset] -> IO (Map.Map TxId Event) +processTransactions clients clientDatasets = do let processors = zip (zip clientDatasets (cycle clients)) [1 ..] mconcat <$> mapConcurrently (uncurry clientProcessDataset) processors where diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 821bab155de..a7c7d6b7dcb 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -45,7 +45,6 @@ main = do numberOfTxs <- generate $ scale (* scalingFactor) getSize dataset <- generateDemoUTxODataset numberOfTxs nodeSocket let datasetPath = fromMaybe workDir outputDirectory "demo-dataset.json" - createDirectoryIfMissing True workDir saveDataset datasetPath dataset where play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index e9f72a52273..6a6683b3665 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -15,7 +15,7 @@ import Hydra.Cluster.Faucet (FaucetException (..)) import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds, defaultNetworkId) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) -import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer) +import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer) import Test.QuickCheck (choose, generate, sized) networkId :: NetworkId @@ -149,10 +149,8 @@ generateDemoUTxODataset nTxns nodeSocket = do pure $ Dataset{fundingTransaction = Nothing, clientDatasets = clientDatasets, title = Nothing, description = Nothing} where generateClientDataset :: ClientKeys -> IO ClientDataset - generateClientDataset clientKeys@ClientKeys{signingKey, externalSigningKey} = do - -- TODO: Missing witnesses. - -- bench-e2e: SubmitTxValidationError (TxValidationErrorInCardanoMode (ShelleyTxValidationError ShelleyBasedEraBabbage (Appl6ace848155a0e967af64f4d00cf8acee8adc95a6b0d"}])))) :| [])))) - let clientVk = getVerificationKey signingKey + generateClientDataset clientKeys@ClientKeys{externalSigningKey} = do + let clientVk = getVerificationKey externalSigningKey initialUTxO <- queryUTxOFor networkId nodeSocket QueryTip clientVk -- FIXME: Improve error when (null initialUTxO) $ do diff --git a/hydra-node/src/Hydra/Ledger/Cardano.hs b/hydra-node/src/Hydra/Ledger/Cardano.hs index 04bbf60be0e..f1f9d54e28a 100644 --- a/hydra-node/src/Hydra/Ledger/Cardano.hs +++ b/hydra-node/src/Hydra/Ledger/Cardano.hs @@ -300,24 +300,24 @@ generateOneRandomTransfer :: Gen (UTxO, SigningKey PaymentKey, [Tx]) generateOneRandomTransfer networkId senderUtxO nbrTx = do recipient <- genKeyPair - generateOneTransfer networkId (snd recipient) senderUtxO nbrTx + pure $ mkOneTransfer networkId (snd recipient) senderUtxO nbrTx -generateOneSelfTransfer :: +mkOneSelfTransfer :: NetworkId -> (UTxO, SigningKey PaymentKey, [Tx]) -> Int -> - Gen (UTxO, SigningKey PaymentKey, [Tx]) -generateOneSelfTransfer networkId senderUtxO nbrTx = do + (UTxO, SigningKey PaymentKey, [Tx]) +mkOneSelfTransfer networkId senderUtxO nbrTx = let (_, recipientSk, _) = senderUtxO - generateOneTransfer networkId recipientSk senderUtxO nbrTx + in mkOneTransfer networkId recipientSk senderUtxO nbrTx -generateOneTransfer :: +mkOneTransfer :: NetworkId -> SigningKey PaymentKey -> (UTxO, SigningKey PaymentKey, [Tx]) -> Int -> - Gen (UTxO, SigningKey PaymentKey, [Tx]) -generateOneTransfer networkId recipientSk (utxo, sender, txs) _ = do + (UTxO, SigningKey PaymentKey, [Tx]) +mkOneTransfer networkId recipientSk (utxo, sender, txs) _ = do let recipientVk = getVerificationKey recipientSk -- NOTE(AB): elements is partial, it crashes if given an empty list, We don't expect -- this function to be ever used in production, and crash will be caught in tests @@ -325,8 +325,7 @@ generateOneTransfer networkId recipientSk (utxo, sender, txs) _ = do [txin] -> case mkSimpleTx txin (mkVkAddress networkId recipientVk, balance @Tx utxo) sender of Left e -> error $ "Tx construction failed: " <> show e <> ", utxo: " <> show utxo - Right tx -> - pure (utxoFromTx tx, recipientSk, tx : txs) + Right tx -> (utxoFromTx tx, recipientSk, tx : txs) _ -> error "Couldn't generate transaction sequence: need exactly one UTXO." From c17ecfb4cbb8f0cee8d3086a1e3a72da8faffae5 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 22 Aug 2024 15:24:27 +0100 Subject: [PATCH 076/115] WIP back to generating the dataset on the fly --- demo/export-tx-id-and-pparams.sh | 72 ++++++++++++++++++ demo/seed-devnet.sh | 38 +--------- hydra-cluster/bench/Bench/Options.hs | 31 +------- hydra-cluster/bench/Main.hs | 55 +++++++++----- hydra-cluster/src/Hydra/Generator.hs | 105 +++++++++++++++++---------- 5 files changed, 180 insertions(+), 121 deletions(-) create mode 100755 demo/export-tx-id-and-pparams.sh diff --git a/demo/export-tx-id-and-pparams.sh b/demo/export-tx-id-and-pparams.sh new file mode 100755 index 00000000000..13c0b462356 --- /dev/null +++ b/demo/export-tx-id-and-pparams.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +set -eo pipefail + +SCRIPT_DIR=${SCRIPT_DIR:-$(realpath $(dirname $(realpath $0)))} +NETWORK_ID=42 + +CCLI_CMD= +DEVNET_DIR=/devnet +if [[ -n ${1} ]]; then + echo >&2 "Using provided cardano-cli command: ${1}" + $(${1} version > /dev/null) + CCLI_CMD=${1} + DEVNET_DIR=${SCRIPT_DIR}/devnet +fi + +HYDRA_NODE_CMD= +if [[ -n ${2} ]]; then + echo >&2 "Using provided hydra-node command: ${2}" + ${2} --version > /dev/null + HYDRA_NODE_CMD=${2} +fi + +# Invoke hydra-node in a container or via provided executable +function hnode() { + if [[ -n ${HYDRA_NODE_CMD} ]]; then + ${HYDRA_NODE_CMD} ${@} + else + docker run --rm \ + --pull always \ + -v ${SCRIPT_DIR}/devnet:/devnet \ + ghcr.io/cardano-scaling/hydra-node:0.18.1 -- ${@} + fi +} + +function publishReferenceScripts() { + echo >&2 "Publishing reference scripts..." + hnode publish-scripts \ + --testnet-magic ${NETWORK_ID} \ + --node-socket ${DEVNET_DIR}/node.socket \ + --cardano-signing-key devnet/credentials/faucet.sk +} + +# Invoke cardano-cli in running cardano-node container or via provided cardano-cli +function ccli() { + ccli_ ${@} --testnet-magic ${NETWORK_ID} +} +function ccli_() { + if [[ -x ${CCLI_CMD} ]]; then + ${CCLI_CMD} ${@} + else + ${DOCKER_COMPOSE_CMD} exec cardano-node cardano-cli ${@} + fi +} + +function queryPParams() { + echo >&2 "Query Protocol parameters" + if [[ -x ${CCLI_CMD} ]]; then + ccli query protocol-parameters --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ + | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json + else + docker exec demo-cardano-node-1 cardano-cli query protocol-parameters --testnet-magic ${NETWORK_ID} --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ + | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json + fi + echo >&2 "Saved in protocol-parameters.json" +} + +queryPParams + +echo "HYDRA_SCRIPTS_TX_ID=$(publishReferenceScripts)" > .env +echo >&2 "Environment variable stored in '.env'" +echo >&2 -e "\n\t$(cat .env)\n" diff --git a/demo/seed-devnet.sh b/demo/seed-devnet.sh index 6fcc14b557d..a7a9c32c024 100755 --- a/demo/seed-devnet.sh +++ b/demo/seed-devnet.sh @@ -43,18 +43,6 @@ function ccli_() { fi } -# Invoke hydra-node in a container or via provided executable -function hnode() { - if [[ -n ${HYDRA_NODE_CMD} ]]; then - ${HYDRA_NODE_CMD} ${@} - else - docker run --rm \ - --pull always \ - -v ${SCRIPT_DIR}/devnet:/devnet \ - ghcr.io/cardano-scaling/hydra-node:0.18.1 -- ${@} - fi -} - # Retrieve some lovelace from faucet function seedFaucet() { ACTOR=${1} @@ -89,26 +77,6 @@ function seedFaucet() { echo >&2 "Done" } -function publishReferenceScripts() { - echo >&2 "Publishing reference scripts..." - hnode publish-scripts \ - --testnet-magic ${NETWORK_ID} \ - --node-socket ${DEVNET_DIR}/node.socket \ - --cardano-signing-key devnet/credentials/faucet.sk -} - -function queryPParams() { - echo >&2 "Query Protocol parameters" - if [[ -x ${CCLI_CMD} ]]; then - ccli query protocol-parameters --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ - | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json - else - docker exec demo-cardano-node-1 cardano-cli query protocol-parameters --testnet-magic ${NETWORK_ID} --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ - | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json - fi - echo >&2 "Saved in protocol-parameters.json" -} - echo >&2 "Fueling up hydra nodes of alice, bob and carol..." seedFaucet "alice" 30000000 # 30 Ada to the node seedFaucet "bob" 30000000 # 30 Ada to the node @@ -117,7 +85,5 @@ echo >&2 "Distributing funds to alice, bob and carol..." seedFaucet "alice-funds" 100000000 # 100 Ada to commit seedFaucet "bob-funds" 50000000 # 50 Ada to commit seedFaucet "carol-funds" 25000000 # 25 Ada to commit -queryPParams -echo "HYDRA_SCRIPTS_TX_ID=$(publishReferenceScripts)" > .env -echo >&2 "Environment variable stored in '.env'" -echo >&2 -e "\n\t$(cat .env)\n" + +./export-tx-id-and-pparams.sh diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index 8c180e91027..bbf17e1e9eb 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -26,16 +26,11 @@ data Options , startingNodeId :: Int } | DemoOptions - { datasetFiles :: [FilePath] - , outputDirectory :: Maybe FilePath - , timeoutSeconds :: NominalDiffTime - , nodeSocket :: SocketPath - , hydraClients :: [Host] - } - | DemoDatasetOptions { outputDirectory :: Maybe FilePath , scalingFactor :: Int + , timeoutSeconds :: NominalDiffTime , nodeSocket :: SocketPath + , hydraClients :: [Host] } benchOptionsParser :: ParserInfo Options @@ -45,7 +40,6 @@ benchOptionsParser = ( command "single" standaloneOptionsInfo <> command "datasets" datasetOptionsInfo <> command "demo" demoOptionsInfo - <> command "demo-datasets" demoDatasetOptionsInfo ) <**> helper ) @@ -165,31 +159,14 @@ demoOptionsInfo = \ * three hydra nodes listening on ports 4001, 4002 and 4003." ) -demoDatasetOptionsInfo :: ParserInfo Options -demoDatasetOptionsInfo = - info - demoDatasetOptionsParser - ( progDesc - "Generate demo bench dataset. \ - \ This requires having cardano node running in the background \ - \ on specified node-socket." - ) - demoOptionsParser :: Parser Options demoOptionsParser = DemoOptions - <$> many filepathParser - <*> optional outputDirectoryParser - <*> timeoutParser - <*> nodeSocketParser - <*> many hydraClientsParser - -demoDatasetOptionsParser :: Parser Options -demoDatasetOptionsParser = - DemoDatasetOptions <$> optional outputDirectoryParser <*> scalingFactorParser + <*> timeoutParser <*> nodeSocketParser + <*> many hydraClientsParser hydraClientsParser :: Parser Host hydraClientsParser = diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index a7c7d6b7dcb..fb0f4cc2713 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -9,8 +9,9 @@ import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) -import Hydra.Cluster.Fixture (defaultNetworkId) -import Hydra.Generator (Dataset (..), generateConstantUTxODataset, generateDemoUTxODataset) +import Hydra.Cluster.Util (keysFor) +import Hydra.Cluster.Fixture (defaultNetworkId, Actor (..)) +import Hydra.Generator (ClientKeys(..), Dataset (..), generateConstantUTxODataset, generateDemoUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) @@ -36,16 +37,20 @@ main = do DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do let action = bench startingNodeId timeoutSeconds run outputDirectory datasetFiles action - DemoOptions{datasetFiles, outputDirectory, timeoutSeconds, nodeSocket, hydraClients} -> do + DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, nodeSocket, hydraClients} -> do let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraClients - run outputDirectory datasetFiles action - DemoDatasetOptions{outputDirectory, scalingFactor, nodeSocket} -> do - workDir <- createSystemTempDirectory "demo-bench" - putStrLn $ "Generating single dataset in work directory: " <> workDir + -- TODO: Maybe all this should be moved down somewhere. numberOfTxs <- generate $ scale (* scalingFactor) getSize - dataset <- generateDemoUTxODataset numberOfTxs nodeSocket - let datasetPath = fromMaybe workDir outputDirectory "demo-dataset.json" - saveDataset datasetPath dataset + let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] + let toClientKeys (actor, funds) = do + sk <- snd <$> keysFor actor + fundsSk <- snd <$> keysFor funds + pure $ ClientKeys sk fundsSk + clientKeys <- forM actors toClientKeys + + dataset <- generateDemoUTxODataset clientKeys numberOfTxs + withTempDir "bench-demo" $ + runSingle outputDirectory dataset action where play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do putStrLn $ "Generating single dataset in work directory: " <> workDir @@ -62,19 +67,31 @@ main = do let action = bench startingNodeId timeoutSeconds run outputDirectory [datasetPath] action + -- TODO: Needs a bit of a refactor. + runSingle' dataset action dir = do + withArgs [] $ do + try @_ @HUnitFailure (action dir dataset) >>= \case + Left exc -> pure $ Left (dataset, dir, TestFailed exc) + Right summary@Summary{numberOfInvalidTxs} + | numberOfInvalidTxs == 0 -> pure $ Right summary + | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) + + runSingle outputDirectory dataset action dir = do + results <- runSingle' dataset action dir + let (failures, summaries) = partitionEithers [results] + -- TODO: Duplicated below; needs to be refactored. + case failures of + [] -> benchmarkSucceeded outputDirectory summaries + errs -> mapM_ (\(_, d, exc) -> benchmarkFailedWith d exc) errs >> exitFailure + run outputDirectory datasetFiles action = do results <- forM datasetFiles $ \datasetPath -> do putTextLn $ "Running benchmark with dataset " <> show datasetPath dataset <- loadDataset datasetPath - withTempDir ("bench-" <> takeFileName datasetPath) $ \dir -> - withArgs [] $ do - -- XXX: Wait between each bench run to give the OS time to cleanup resources?? - threadDelay 10 - try @_ @HUnitFailure (action dir dataset) >>= \case - Left exc -> pure $ Left (dataset, dir, TestFailed exc) - Right summary@Summary{numberOfInvalidTxs} - | numberOfInvalidTxs == 0 -> pure $ Right summary - | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) + withTempDir ("bench-" <> takeFileName datasetPath) $ \dir -> do + -- XXX: Wait between each bench run to give the OS time to cleanup resources?? + threadDelay 10 + runSingle' dataset action dir let (failures, summaries) = partitionEithers results case failures of [] -> benchmarkSucceeded outputDirectory summaries diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 6a6683b3665..4a9426ddede 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -93,20 +93,43 @@ generateConstantUTxODataset nClients nTxs = do (_, faucetSk) <- keysFor Faucet generate $ genDatasetConstantUTxO faucetSk nClients nTxs -genDatasetConstantUTxO :: - -- | The faucet signing key - SigningKey PaymentKey -> +-- TODO: Refactor +generateDemoUTxODataset :: -- | Number of clients + [ClientKeys] -> + -- | Number of transactions Int -> + IO Dataset +generateDemoUTxODataset allClientKeys nTxs = do + (_, faucetSk) <- keysFor Faucet + generate $ genDemoUTxODataset allClientKeys faucetSk nTxs + +genDatasetConstantUTxO :: SigningKey PaymentKey -> Int -> Int -> Gen Dataset +genDatasetConstantUTxO faucetSk nClients nTxns = do + clientKeys <- replicateM nClients arbitrary + genDatasetConstantUTxO' initialInput faucetSk clientKeys nTxns + where + initialInput = + genesisUTxOPseudoTxIn + networkId + (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) + +genDatasetConstantUTxO' :: + -- | The initial transaction + TxIn -> + -- | The faucet signing key + SigningKey PaymentKey -> + -- | Clients + [ClientKeys] -> -- | Number of transactions Int -> Gen Dataset -genDatasetConstantUTxO faucetSk nClients nTxs = do - clientKeys <- replicateM nClients arbitrary +genDatasetConstantUTxO' initialInput faucetSk allClientKeys nTxs = do -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the benchmark run. - clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do + let nClients = length allClientKeys + clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) pure (getVerificationKey externalSigningKey, amount) let fundingTransaction = @@ -116,14 +139,9 @@ genDatasetConstantUTxO faucetSk nClients nTxs = do faucetSk (Coin availableInitialFunds) clientFunds - clientDatasets <- forM clientKeys (generateClientDataset fundingTransaction) + clientDatasets <- forM allClientKeys (generateClientDataset fundingTransaction) pure Dataset{fundingTransaction = Just fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where - initialInput = - genesisUTxOPseudoTxIn - networkId - (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) - generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction txSequence <- @@ -132,33 +150,42 @@ genDatasetConstantUTxO faucetSk nClients nTxs = do <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] pure ClientDataset{clientKeys, initialUTxO, txSequence} -generateDemoUTxODataset :: - Int -> - SocketPath -> - IO Dataset -generateDemoUTxODataset nTxns nodeSocket = do - -- FIXME: Client keys hard-coded; could read from arguments. - clientKeys <- do - let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] - let toClientKeys (actor, funds) = do - sk <- snd <$> keysFor actor - fundsSk <- snd <$> keysFor funds - pure $ ClientKeys sk fundsSk - forM actors toClientKeys - clientDatasets <- forM clientKeys generateClientDataset - pure $ Dataset{fundingTransaction = Nothing, clientDatasets = clientDatasets, title = Nothing, description = Nothing} - where - generateClientDataset :: ClientKeys -> IO ClientDataset - generateClientDataset clientKeys@ClientKeys{externalSigningKey} = do - let clientVk = getVerificationKey externalSigningKey - initialUTxO <- queryUTxOFor networkId nodeSocket QueryTip clientVk - -- FIXME: Improve error - when (null initialUTxO) $ do - error "No initial UTxOs. Did you seed your devnet?" - generate $ do - txSequence <- - reverse . thrd <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, mempty) [1 .. nTxns] - pure ClientDataset{clientKeys, initialUTxO, txSequence} +genDemoUTxODataset :: [ClientKeys] -> SigningKey PaymentKey -> Int -> Gen Dataset +genDemoUTxODataset allClientKeys faucetSk nTxns = do + genDatasetConstantUTxO' initialInput faucetSk allClientKeys nTxns + where + initialInput = + genesisUTxOPseudoTxIn + networkId + (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) + +-- generateDemoUTxODataset :: +-- Int -> +-- SocketPath -> +-- IO Dataset +-- generateDemoUTxODataset nTxns nodeSocket = do +-- -- FIXME: Client keys hard-coded; could read from arguments. +-- clientKeys <- do +-- let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] +-- let toClientKeys (actor, funds) = do +-- sk <- snd <$> keysFor actor +-- fundsSk <- snd <$> keysFor funds +-- pure $ ClientKeys sk fundsSk +-- forM actors toClientKeys +-- clientDatasets <- forM clientKeys generateClientDataset +-- pure $ Dataset{fundingTransaction = Nothing, clientDatasets = clientDatasets, title = Nothing, description = Nothing} +-- where +-- generateClientDataset :: ClientKeys -> IO ClientDataset +-- generateClientDataset clientKeys@ClientKeys{externalSigningKey} = do +-- let clientVk = getVerificationKey externalSigningKey +-- initialUTxO <- queryUTxOFor networkId nodeSocket QueryTip clientVk +-- -- FIXME: Improve error +-- when (null initialUTxO) $ do +-- error "No initial UTxOs. Did you seed your devnet?" +-- generate $ do +-- txSequence <- +-- reverse . thrd <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, mempty) [1 .. nTxns] +-- pure ClientDataset{clientKeys, initialUTxO, txSequence} -- * Helpers thrd :: (a, b, c) -> c From b98ad0ad603e52244e22c1195b1e91034a60049e Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 23 Aug 2024 00:58:44 +0200 Subject: [PATCH 077/115] Do not pre-seed devnet Instead, let the scenario seed the network and provide funds to running clients. At the end, funds are returned back to the faucet. --- .github/workflows/network-test.yaml | 17 +-- demo/publish-script-devnet.sh | 81 ++++++++++++ hydra-cluster/bench/Bench/EndToEnd.hs | 42 +++++-- hydra-cluster/bench/Main.hs | 21 ++-- hydra-cluster/src/Hydra/Cluster/Faucet.hs | 31 +++-- hydra-cluster/src/Hydra/Generator.hs | 144 +++++++++------------- hydra-cluster/test/Test/GeneratorSpec.hs | 6 +- hydra-node/src/Hydra/Ledger/Cardano.hs | 8 +- 8 files changed, 216 insertions(+), 134 deletions(-) create mode 100755 demo/publish-script-devnet.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index b3466dff2bd..23baa3aabe4 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -45,14 +45,14 @@ jobs: cd demo ./prepare-devnet.sh docker compose up -d cardano-node + sleep 5 # :tear: socket permissions. sudo chown runner:docker devnet/node.socket - - ./seed-devnet.sh + ./publish-script-devnet.sh # Specify two docker compose yamls; the second one overrides the # images to use the netem ones specifically docker compose -f docker-compose.yaml -f docker-compose-netem.yaml up -d hydra-node-{1,2,3} - sleep 10 + sleep 3 docker ps - name: Build required nix and docker derivations @@ -88,22 +88,15 @@ jobs: --target 172.16.238.20 \ --target 172.16.238.30 \ loss \ - --percent 75 "re2:hydra-node-1" & + --percent 1 "re2:hydra-node-1" & .github/workflows/network/watch_logs.sh - # TODO: make this a flake output / easier accesible - mkdir $(pwd)/datasets && nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ - demo-datasets \ - --output-directory=$(pwd)/datasets \ - --scaling-factor=10 \ - --node-socket=demo/devnet/node.socket - # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ - $(pwd)/datasets/demo-dataset.json \ --output-directory=$(pwd)/benchmarks \ + --scaling-factor=10 \ --timeout=1000s \ --node-socket=demo/devnet/node.socket \ --hydra-client=localhost:4001 \ diff --git a/demo/publish-script-devnet.sh b/demo/publish-script-devnet.sh new file mode 100755 index 00000000000..aec499bb0ed --- /dev/null +++ b/demo/publish-script-devnet.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# Seed a "devnet" by distributing Ada to hydra nodes +set -eo pipefail + +SCRIPT_DIR=${SCRIPT_DIR:-$(realpath $(dirname $(realpath $0)))} +NETWORK_ID=42 + +CCLI_CMD= +DEVNET_DIR=/devnet +if [[ -n ${1} ]]; then + echo >&2 "Using provided cardano-cli command: ${1}" + $(${1} version > /dev/null) + CCLI_CMD=${1} + DEVNET_DIR=${SCRIPT_DIR}/devnet +fi + +HYDRA_NODE_CMD= +if [[ -n ${2} ]]; then + echo >&2 "Using provided hydra-node command: ${2}" + ${2} --version > /dev/null + HYDRA_NODE_CMD=${2} +fi + +DOCKER_COMPOSE_CMD= +if [[ ! -x ${CCLI_CMD} ]]; then + if docker compose --version > /dev/null 2>&1; then + DOCKER_COMPOSE_CMD="docker compose" + else + DOCKER_COMPOSE_CMD="docker-compose" + fi +fi + +# Invoke cardano-cli in running cardano-node container or via provided cardano-cli +function ccli() { + ccli_ ${@} --testnet-magic ${NETWORK_ID} +} +function ccli_() { + if [[ -x ${CCLI_CMD} ]]; then + ${CCLI_CMD} ${@} + else + ${DOCKER_COMPOSE_CMD} exec cardano-node cardano-cli ${@} + fi +} + +# Invoke hydra-node in a container or via provided executable +function hnode() { + if [[ -n ${HYDRA_NODE_CMD} ]]; then + ${HYDRA_NODE_CMD} ${@} + else + docker run --rm \ + --pull always \ + -v ${SCRIPT_DIR}/devnet:/devnet \ + ghcr.io/cardano-scaling/hydra-node:0.18.1 -- ${@} + fi +} + +function publishReferenceScripts() { + echo >&2 "Publishing reference scripts..." + hnode publish-scripts \ + --testnet-magic ${NETWORK_ID} \ + --node-socket ${DEVNET_DIR}/node.socket \ + --cardano-signing-key devnet/credentials/faucet.sk +} + +function queryPParams() { + echo >&2 "Query Protocol parameters" + if [[ -x ${CCLI_CMD} ]]; then + ccli query protocol-parameters --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ + | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json + else + docker exec demo-cardano-node-1 cardano-cli query protocol-parameters --testnet-magic ${NETWORK_ID} --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ + | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json + fi + echo >&2 "Saved in protocol-parameters.json" +} + +queryPParams +echo "HYDRA_SCRIPTS_TX_ID=$(publishReferenceScripts)" > .env +echo >&2 "Environment variable stored in '.env'" +echo >&2 -e "\n\t$(cat .env)\n" diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 93a825db75c..f3aeb323928 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -29,7 +29,7 @@ import Data.Set ((\\)) import Data.Set qualified as Set import Data.Time (UTCTime (UTCTime), utctDayTime) import Hydra.Cardano.Api (NetworkId, SocketPath, Tx, TxId, UTxO, getVerificationKey, signTx) -import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, seedFromFaucet) +import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, returnFundsToFaucet', seedFromFaucet) import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Scenarios ( EndToEndLog (..), @@ -40,7 +40,7 @@ import Hydra.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) import Hydra.Crypto (generateSigningKey) import Hydra.Generator (ClientDataset (..), ClientKeys (..), Dataset (..)) import Hydra.Ledger (txId) -import Hydra.Logging (Tracer, withTracerOutputTo) +import Hydra.Logging (Tracer, traceWith, withTracerOutputTo) import Hydra.Network (Host) import Hydra.Party (Party, deriveParty) import HydraNode ( @@ -110,7 +110,7 @@ benchDemo :: FilePath -> Dataset -> IO Summary -benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset = do +benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Dataset{clientDatasets, fundingTransaction} = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> @@ -121,12 +121,21 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset = do Nothing -> error ("Not found running node at socket: " <> show nodeSocket <> ", and network: " <> show networkId) Just node -> do - putStrLn $ "Connecting to hydra cluster: " <> show hydraClients - let hydraTracer = contramap FromHydraNode tracer - withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case - [] -> error "no hydra clients provided" - (leader : followers) -> - scenario hydraTracer node workDir dataset mempty leader followers + let clientSks = clientKeys <$> clientDatasets + (`finally` returnFaucetFunds tracer node clientSks) $ do + putTextLn "Seeding network" + submitTransaction networkId nodeSocket fundingTransaction + void $ awaitTransaction networkId nodeSocket fundingTransaction + forM_ clientSks $ \ClientKeys{signingKey} -> do + let vk = getVerificationKey signingKey + putTextLn $ "Seed client " <> show vk + seedFromFaucet node vk 100_000_000 (contramap FromFaucet tracer) + putStrLn $ "Connecting to hydra cluster: " <> show hydraClients + let hydraTracer = contramap FromHydraNode tracer + withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case + [] -> error "no hydra clients provided" + (leader : followers) -> + scenario hydraTracer node workDir dataset mempty leader followers where withHydraClientConnections tracer peers connections action = do case peers of @@ -135,6 +144,16 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset = do withConnectionToNodeHost tracer peerId peer False $ \con -> do withHydraClientConnections tracer rest (con : connections) action + returnFaucetFunds tracer node cKeys = do + putTextLn "Returning funds to faucet" + let faucetTracer = contramap FromFaucet tracer + let senders = concatMap @[] (\(ClientKeys sk esk) -> [sk, esk]) cKeys + mapM_ + ( \sender -> do + returnAmount <- returnFundsToFaucet' faucetTracer node sender + traceWith faucetTracer $ ReturnedFunds{actor = show sender, returnAmount} + ) + senders scenario :: Tracer IO HydraNodeLog -> RunningNode -> @@ -297,9 +316,8 @@ seedNetwork node@RunningNode{nodeSocket, networkId} Dataset{fundingTransaction, where fundClients = do putTextLn "Fund scenario from faucet" - let fundingTx = fromMaybe (error "missing fundingTransaction") fundingTransaction - submitTransaction networkId nodeSocket fundingTx - void $ awaitTransaction networkId nodeSocket fundingTx + submitTransaction networkId nodeSocket fundingTransaction + void $ awaitTransaction networkId nodeSocket fundingTransaction fuelWith100Ada ClientKeys{signingKey} = do let vk = getVerificationKey signingKey diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index fb0f4cc2713..a84230203a5 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -9,9 +9,9 @@ import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) +import Hydra.Cluster.Fixture (Actor (..), defaultNetworkId) import Hydra.Cluster.Util (keysFor) -import Hydra.Cluster.Fixture (defaultNetworkId, Actor (..)) -import Hydra.Generator (ClientKeys(..), Dataset (..), generateConstantUTxODataset, generateDemoUTxODataset) +import Hydra.Generator (ClientKeys (..), Dataset (..), generateConstantUTxODataset, generateDemoUTxODataset) import Options.Applicative (execParser) import System.Directory (createDirectoryIfMissing, doesDirectoryExist) import System.Environment (withArgs) @@ -48,14 +48,15 @@ main = do pure $ ClientKeys sk fundsSk clientKeys <- forM actors toClientKeys - dataset <- generateDemoUTxODataset clientKeys numberOfTxs + dataset <- generateDemoUTxODataset nodeSocket clientKeys numberOfTxs withTempDir "bench-demo" $ runSingle outputDirectory dataset action where play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do + (_, faucetSk) <- keysFor Faucet putStrLn $ "Generating single dataset in work directory: " <> workDir numberOfTxs <- generate $ scale (* scalingFactor) getSize - dataset <- generateConstantUTxODataset (fromIntegral clusterSize) numberOfTxs + dataset <- generate $ generateConstantUTxODataset faucetSk (fromIntegral clusterSize) numberOfTxs let datasetPath = workDir "dataset.json" saveDataset datasetPath dataset let action = bench startingNodeId timeoutSeconds @@ -69,12 +70,12 @@ main = do -- TODO: Needs a bit of a refactor. runSingle' dataset action dir = do - withArgs [] $ do - try @_ @HUnitFailure (action dir dataset) >>= \case - Left exc -> pure $ Left (dataset, dir, TestFailed exc) - Right summary@Summary{numberOfInvalidTxs} - | numberOfInvalidTxs == 0 -> pure $ Right summary - | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) + withArgs [] $ do + try @_ @HUnitFailure (action dir dataset) >>= \case + Left exc -> pure $ Left (dataset, dir, TestFailed exc) + Right summary@Summary{numberOfInvalidTxs} + | numberOfInvalidTxs == 0 -> pure $ Right summary + | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) runSingle outputDirectory dataset action dir = do results <- runSingle' dataset action dir diff --git a/hydra-cluster/src/Hydra/Cluster/Faucet.hs b/hydra-cluster/src/Hydra/Cluster/Faucet.hs index 4d62dee0e4e..59c20e4a96c 100644 --- a/hydra-cluster/src/Hydra/Cluster/Faucet.hs +++ b/hydra-cluster/src/Hydra/Cluster/Faucet.hs @@ -106,19 +106,30 @@ returnFundsToFaucet :: RunningNode -> Actor -> IO () -returnFundsToFaucet tracer RunningNode{networkId, nodeSocket} sender = do +returnFundsToFaucet tracer node sender = do + senderKeys <- keysFor sender + returnAmount <- returnFundsToFaucet' tracer node (snd senderKeys) + traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount} + +returnFundsToFaucet' :: + Tracer IO FaucetLog -> + RunningNode -> + SigningKey PaymentKey -> + IO Coin +returnFundsToFaucet' tracer RunningNode{networkId, nodeSocket} senderSk = do (faucetVk, _) <- keysFor Faucet let faucetAddress = mkVkAddress networkId faucetVk - - (senderVk, senderSk) <- keysFor sender + let senderVk = getVerificationKey senderSk utxo <- queryUTxOFor networkId nodeSocket QueryTip senderVk - unless (null utxo) . retryOnExceptions tracer $ do - let utxoValue = balance @Tx utxo - let allLovelace = selectLovelace utxoValue - tx <- sign senderSk <$> buildTxBody utxo faucetAddress - submitTransaction networkId nodeSocket tx - void $ awaitTransaction networkId nodeSocket tx - traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount = allLovelace} + if null utxo + then pure 0 + else retryOnExceptions tracer $ do + let utxoValue = balance @Tx utxo + let allLovelace = selectLovelace utxoValue + tx <- sign senderSk <$> buildTxBody utxo faucetAddress + submitTransaction networkId nodeSocket tx + void $ awaitTransaction networkId nodeSocket tx + pure allLovelace where buildTxBody utxo faucetAddress = -- Here we specify no outputs in the transaction so that a change output with the diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 4a9426ddede..b3727ac3bf2 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -5,17 +5,15 @@ import Hydra.Prelude hiding (size) import Cardano.Api.Ledger (PParams) import Cardano.Api.UTxO qualified as UTxO -import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryProtocolParameters, queryUTxOFor, sign) +import CardanoClient (QueryPoint (QueryTip), buildRawTransaction, buildTransaction, queryUTxOFor, sign) import Control.Monad (foldM) import Data.Aeson (object, withObject, (.:), (.=)) import Data.Default (def) -import Data.List qualified as List -import Hydra.Cardano.Api.Pretty (renderTx) import Hydra.Cluster.Faucet (FaucetException (..)) -import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds, defaultNetworkId) +import Hydra.Cluster.Fixture (Actor (..), availableInitialFunds) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) -import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer) +import Hydra.Ledger.Cardano (genSigningKey, generateOneRandomTransfer, generateOneSelfTransfer) import Test.QuickCheck (choose, generate, sized) networkId :: NetworkId @@ -26,7 +24,7 @@ networkId = Testnet $ NetworkMagic 42 -- against one or more `HydraNode`s. A dataset can optionally have a `title` and `description` -- which will be used to report results. data Dataset = Dataset - { fundingTransaction :: Maybe Tx + { fundingTransaction :: Tx , clientDatasets :: [ClientDataset] , title :: Maybe Text , description :: Maybe Text @@ -37,7 +35,7 @@ data Dataset = Dataset instance Arbitrary Dataset where arbitrary = sized $ \n -> do sk <- genSigningKey - genDatasetConstantUTxO sk (n `div` 10) n + generateConstantUTxODataset sk (n `div` 10) n data ClientKeys = ClientKeys { signingKey :: SigningKey PaymentKey @@ -84,52 +82,19 @@ defaultProtocolParameters = def -- The sequence of transactions generated consist only of simple payments from -- and to arbitrary keys controlled by the individual clients. generateConstantUTxODataset :: + -- | Faucet signing key + SigningKey PaymentKey -> -- | Number of clients Int -> -- | Number of transactions Int -> - IO Dataset -generateConstantUTxODataset nClients nTxs = do - (_, faucetSk) <- keysFor Faucet - generate $ genDatasetConstantUTxO faucetSk nClients nTxs - --- TODO: Refactor -generateDemoUTxODataset :: - -- | Number of clients - [ClientKeys] -> - -- | Number of transactions - Int -> - IO Dataset -generateDemoUTxODataset allClientKeys nTxs = do - (_, faucetSk) <- keysFor Faucet - generate $ genDemoUTxODataset allClientKeys faucetSk nTxs - -genDatasetConstantUTxO :: SigningKey PaymentKey -> Int -> Int -> Gen Dataset -genDatasetConstantUTxO faucetSk nClients nTxns = do - clientKeys <- replicateM nClients arbitrary - genDatasetConstantUTxO' initialInput faucetSk clientKeys nTxns - where - initialInput = - genesisUTxOPseudoTxIn - networkId - (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) - -genDatasetConstantUTxO' :: - -- | The initial transaction - TxIn -> - -- | The faucet signing key - SigningKey PaymentKey -> - -- | Clients - [ClientKeys] -> - -- | Number of transactions - Int -> Gen Dataset -genDatasetConstantUTxO' initialInput faucetSk allClientKeys nTxs = do +generateConstantUTxODataset faucetSk nClients nTxs = do + clientKeys <- replicateM nClients arbitrary -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the benchmark run. - let nClients = length allClientKeys - clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do + clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) pure (getVerificationKey externalSigningKey, amount) let fundingTransaction = @@ -139,9 +104,14 @@ genDatasetConstantUTxO' initialInput faucetSk allClientKeys nTxs = do faucetSk (Coin availableInitialFunds) clientFunds - clientDatasets <- forM allClientKeys (generateClientDataset fundingTransaction) - pure Dataset{fundingTransaction = Just fundingTransaction, clientDatasets, title = Nothing, description = Nothing} + clientDatasets <- forM clientKeys (generateClientDataset fundingTransaction) + pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where + initialInput = + genesisUTxOPseudoTxIn + networkId + (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) + generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction txSequence <- @@ -150,42 +120,50 @@ genDatasetConstantUTxO' initialInput faucetSk allClientKeys nTxs = do <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] pure ClientDataset{clientKeys, initialUTxO, txSequence} -genDemoUTxODataset :: [ClientKeys] -> SigningKey PaymentKey -> Int -> Gen Dataset -genDemoUTxODataset allClientKeys faucetSk nTxns = do - genDatasetConstantUTxO' initialInput faucetSk allClientKeys nTxns - where - initialInput = - genesisUTxOPseudoTxIn - networkId - (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) - --- generateDemoUTxODataset :: --- Int -> --- SocketPath -> --- IO Dataset --- generateDemoUTxODataset nTxns nodeSocket = do --- -- FIXME: Client keys hard-coded; could read from arguments. --- clientKeys <- do --- let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] --- let toClientKeys (actor, funds) = do --- sk <- snd <$> keysFor actor --- fundsSk <- snd <$> keysFor funds --- pure $ ClientKeys sk fundsSk --- forM actors toClientKeys --- clientDatasets <- forM clientKeys generateClientDataset --- pure $ Dataset{fundingTransaction = Nothing, clientDatasets = clientDatasets, title = Nothing, description = Nothing} --- where --- generateClientDataset :: ClientKeys -> IO ClientDataset --- generateClientDataset clientKeys@ClientKeys{externalSigningKey} = do --- let clientVk = getVerificationKey externalSigningKey --- initialUTxO <- queryUTxOFor networkId nodeSocket QueryTip clientVk --- -- FIXME: Improve error --- when (null initialUTxO) $ do --- error "No initial UTxOs. Did you seed your devnet?" --- generate $ do --- txSequence <- --- reverse . thrd <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, mempty) [1 .. nTxns] --- pure ClientDataset{clientKeys, initialUTxO, txSequence} +-- TODO: Refactor +generateDemoUTxODataset :: + SocketPath -> + -- | Number of clients + [ClientKeys] -> + -- | Number of transactions + Int -> + IO Dataset +generateDemoUTxODataset nodeSocket allClientKeys nTxs = do + (faucetVk, faucetSk) <- keysFor Faucet + faucetUTxO <- queryUTxOFor networkId nodeSocket QueryTip faucetVk + let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) + let nClients = length allClientKeys + -- Prepare funding transaction which will give every client's + -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get + -- funded in the beginning of the benchmark run. + clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do + amount <- generate $ Coin <$> choose (1, fundsAvailable `div` fromIntegral nClients) + pure (getVerificationKey externalSigningKey, amount) + let recipientOutputs = + flip map clientFunds $ \(vk, ll) -> + TxOut + (mkVkAddress networkId vk) + (lovelaceToValue ll) + TxOutDatumNone + ReferenceScriptNone + let changeAddress = mkVkAddress networkId faucetVk + fundingTransaction <- + buildTransaction networkId nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case + Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} + Right body -> do + let signedTx = sign faucetSk body + pure signedTx + generate $ do + clientDatasets <- forM allClientKeys (generateClientDataset fundingTransaction) + pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} + where + generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do + let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction + txSequence <- + reverse + . thrd + <$> foldM (generateOneSelfTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] + pure ClientDataset{clientKeys, initialUTxO, txSequence} -- * Helpers thrd :: (a, b, c) -> c diff --git a/hydra-cluster/test/Test/GeneratorSpec.hs b/hydra-cluster/test/Test/GeneratorSpec.hs index fd10e174445..93d602cad4b 100644 --- a/hydra-cluster/test/Test/GeneratorSpec.hs +++ b/hydra-cluster/test/Test/GeneratorSpec.hs @@ -13,7 +13,7 @@ import Hydra.Cluster.Util (keysFor) import Hydra.Generator ( ClientDataset (..), Dataset (..), - genDatasetConstantUTxO, + generateConstantUTxODataset, ) import Hydra.Ledger (ChainSlot (ChainSlot), applyTransactions) import Hydra.Ledger.Cardano (Tx, cardanoLedger) @@ -45,8 +45,8 @@ prop_keepsUTxOConstant = let ledgerEnv = newLedgerEnv defaultPParams -- XXX: non-exhaustive pattern match pure $ - forAll (genDatasetConstantUTxO faucetSk 1 n) $ - \Dataset{fundingTransaction = Just fundingTransaction, clientDatasets = [ClientDataset{txSequence}]} -> + forAll (generateConstantUTxODataset faucetSk 1 n) $ + \Dataset{fundingTransaction, clientDatasets = [ClientDataset{txSequence}]} -> let initialUTxO = utxoFromTx fundingTransaction finalUTxO = foldl' (apply defaultGlobals ledgerEnv) initialUTxO txSequence in length finalUTxO == length initialUTxO diff --git a/hydra-node/src/Hydra/Ledger/Cardano.hs b/hydra-node/src/Hydra/Ledger/Cardano.hs index f1f9d54e28a..986ab0399e4 100644 --- a/hydra-node/src/Hydra/Ledger/Cardano.hs +++ b/hydra-node/src/Hydra/Ledger/Cardano.hs @@ -302,14 +302,14 @@ generateOneRandomTransfer networkId senderUtxO nbrTx = do recipient <- genKeyPair pure $ mkOneTransfer networkId (snd recipient) senderUtxO nbrTx -mkOneSelfTransfer :: +generateOneSelfTransfer :: NetworkId -> (UTxO, SigningKey PaymentKey, [Tx]) -> Int -> - (UTxO, SigningKey PaymentKey, [Tx]) -mkOneSelfTransfer networkId senderUtxO nbrTx = + Gen (UTxO, SigningKey PaymentKey, [Tx]) +generateOneSelfTransfer networkId senderUtxO nbrTx = do let (_, recipientSk, _) = senderUtxO - in mkOneTransfer networkId recipientSk senderUtxO nbrTx + pure $ mkOneTransfer networkId recipientSk senderUtxO nbrTx mkOneTransfer :: NetworkId -> From 42d4cdf70451fd1471263fe3f927fc33f0e7e171 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 23 Aug 2024 10:26:34 +0100 Subject: [PATCH 078/115] Use other script for exporting pparams --- .github/workflows/network-test.yaml | 5 +- demo/.gitignore | 2 + demo/export-tx-id-and-pparams.sh | 1 - demo/publish-script-devnet.sh | 81 ----------------------------- 4 files changed, 3 insertions(+), 86 deletions(-) create mode 100644 demo/.gitignore delete mode 100755 demo/publish-script-devnet.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 23baa3aabe4..5b4307583f0 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -48,7 +48,7 @@ jobs: sleep 5 # :tear: socket permissions. sudo chown runner:docker devnet/node.socket - ./publish-script-devnet.sh + ./export-tx-id-and-pparams.sh # Specify two docker compose yamls; the second one overrides the # images to use the netem ones specifically docker compose -f docker-compose.yaml -f docker-compose-netem.yaml up -d hydra-node-{1,2,3} @@ -90,9 +90,6 @@ jobs: loss \ --percent 1 "re2:hydra-node-1" & - .github/workflows/network/watch_logs.sh - - # TODO: make this a flake output / easier accesible nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ --output-directory=$(pwd)/benchmarks \ diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 00000000000..66a99916b32 --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,2 @@ +/benchmarks +/datasets diff --git a/demo/export-tx-id-and-pparams.sh b/demo/export-tx-id-and-pparams.sh index 13c0b462356..37707ecabbd 100755 --- a/demo/export-tx-id-and-pparams.sh +++ b/demo/export-tx-id-and-pparams.sh @@ -66,7 +66,6 @@ function queryPParams() { } queryPParams - echo "HYDRA_SCRIPTS_TX_ID=$(publishReferenceScripts)" > .env echo >&2 "Environment variable stored in '.env'" echo >&2 -e "\n\t$(cat .env)\n" diff --git a/demo/publish-script-devnet.sh b/demo/publish-script-devnet.sh deleted file mode 100755 index aec499bb0ed..00000000000 --- a/demo/publish-script-devnet.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -# Seed a "devnet" by distributing Ada to hydra nodes -set -eo pipefail - -SCRIPT_DIR=${SCRIPT_DIR:-$(realpath $(dirname $(realpath $0)))} -NETWORK_ID=42 - -CCLI_CMD= -DEVNET_DIR=/devnet -if [[ -n ${1} ]]; then - echo >&2 "Using provided cardano-cli command: ${1}" - $(${1} version > /dev/null) - CCLI_CMD=${1} - DEVNET_DIR=${SCRIPT_DIR}/devnet -fi - -HYDRA_NODE_CMD= -if [[ -n ${2} ]]; then - echo >&2 "Using provided hydra-node command: ${2}" - ${2} --version > /dev/null - HYDRA_NODE_CMD=${2} -fi - -DOCKER_COMPOSE_CMD= -if [[ ! -x ${CCLI_CMD} ]]; then - if docker compose --version > /dev/null 2>&1; then - DOCKER_COMPOSE_CMD="docker compose" - else - DOCKER_COMPOSE_CMD="docker-compose" - fi -fi - -# Invoke cardano-cli in running cardano-node container or via provided cardano-cli -function ccli() { - ccli_ ${@} --testnet-magic ${NETWORK_ID} -} -function ccli_() { - if [[ -x ${CCLI_CMD} ]]; then - ${CCLI_CMD} ${@} - else - ${DOCKER_COMPOSE_CMD} exec cardano-node cardano-cli ${@} - fi -} - -# Invoke hydra-node in a container or via provided executable -function hnode() { - if [[ -n ${HYDRA_NODE_CMD} ]]; then - ${HYDRA_NODE_CMD} ${@} - else - docker run --rm \ - --pull always \ - -v ${SCRIPT_DIR}/devnet:/devnet \ - ghcr.io/cardano-scaling/hydra-node:0.18.1 -- ${@} - fi -} - -function publishReferenceScripts() { - echo >&2 "Publishing reference scripts..." - hnode publish-scripts \ - --testnet-magic ${NETWORK_ID} \ - --node-socket ${DEVNET_DIR}/node.socket \ - --cardano-signing-key devnet/credentials/faucet.sk -} - -function queryPParams() { - echo >&2 "Query Protocol parameters" - if [[ -x ${CCLI_CMD} ]]; then - ccli query protocol-parameters --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ - | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json - else - docker exec demo-cardano-node-1 cardano-cli query protocol-parameters --testnet-magic ${NETWORK_ID} --socket-path ${DEVNET_DIR}/node.socket --out-file /dev/stdout \ - | jq ".txFeeFixed = 0 | .txFeePerByte = 0 | .executionUnitPrices.priceMemory = 0 | .executionUnitPrices.priceSteps = 0" > devnet/protocol-parameters.json - fi - echo >&2 "Saved in protocol-parameters.json" -} - -queryPParams -echo "HYDRA_SCRIPTS_TX_ID=$(publishReferenceScripts)" > .env -echo >&2 "Environment variable stored in '.env'" -echo >&2 -e "\n\t$(cat .env)\n" From b76157875ebdd53d76d04593f4fd79b8ffc31512 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 23 Aug 2024 10:37:20 +0100 Subject: [PATCH 079/115] Short contestation period so that it closes fast --- demo/docker-compose.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/demo/docker-compose.yaml b/demo/docker-compose.yaml index b541ff481b2..eb8da146d96 100644 --- a/demo/docker-compose.yaml +++ b/demo/docker-compose.yaml @@ -49,6 +49,7 @@ services: , "--testnet-magic", "42" , "--node-socket", "/devnet/node.socket" , "--persistence-dir", "/devnet/persistence/alice" + , "--contestation-period", "3" ] networks: hydra_net: @@ -85,6 +86,7 @@ services: , "--testnet-magic", "42" , "--node-socket", "/devnet/node.socket" , "--persistence-dir", "/devnet/persistence/bob" + , "--contestation-period", "3" ] networks: hydra_net: @@ -121,6 +123,7 @@ services: , "--testnet-magic", "42" , "--node-socket", "/devnet/node.socket" , "--persistence-dir", "/devnet/persistence/carol" + , "--contestation-period", "3" ] networks: hydra_net: From 79dfee8afce40e6a757b20d2418a9c6dfb72abd0 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 23 Aug 2024 11:58:09 +0200 Subject: [PATCH 080/115] add failure handling on tx processing while head is open this allows us to close the head when we get a time exceeded while wait for all confirmations when processing a tx. --- .github/workflows/network-test.yaml | 2 +- hydra-cluster/bench/Bench/EndToEnd.hs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 5b4307583f0..5d2ace810b1 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -88,7 +88,7 @@ jobs: --target 172.16.238.20 \ --target 172.16.238.30 \ loss \ - --percent 1 "re2:hydra-node-1" & + --percent 75 "re2:hydra-node-1" & nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index f3aeb323928..40d544b2e26 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -346,9 +346,12 @@ processTransactions clients clientDatasets = do submissionQ <- newTBQueueIO (fromIntegral numberOfTxs) registry <- newRegistry atomically $ forM_ txSequence $ writeTBQueue submissionQ - submitTxs client registry submissionQ - `concurrently_` waitForAllConfirmations client registry (Set.fromList $ map txId txSequence) - `concurrently_` progressReport (hydraNodeId client) clientId numberOfTxs submissionQ + ( submitTxs client registry submissionQ + `concurrently_` waitForAllConfirmations client registry (Set.fromList $ map txId txSequence) + `concurrently_` progressReport (hydraNodeId client) clientId numberOfTxs submissionQ + ) + `catch` \(_ :: SomeException) -> + putStrLn "Time exceeded while wait for all confirmations" readTVarIO (processedTxs registry) progressReport :: Int -> Int -> Int -> TBQueue IO Tx -> IO () From 0a1535415ab6d11ff0418fd483d6b5b984dd344e Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 23 Aug 2024 11:42:28 +0100 Subject: [PATCH 081/115] Compute pct of transactions confirmed --- hydra-cluster/bench/Bench/EndToEnd.hs | 3 ++- hydra-cluster/bench/Bench/Summary.hs | 21 +++++++++++++-------- hydra-cluster/hydra-cluster.cabal | 1 + 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 40d544b2e26..1e0a4d9f63c 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -166,6 +166,7 @@ scenario :: scenario hydraTracer node workDir Dataset{clientDatasets, title, description} parties leader followers = do let clusterSize = fromIntegral $ length clientDatasets let clients = leader : followers + let totalTxs = sum $ map (length . txSequence) clientDatasets putTextLn "Initializing Head" send leader $ input "Init" [] @@ -216,7 +217,7 @@ scenario hydraTracer node workDir Dataset{clientDatasets, title, description} pa summaryTitle = fromMaybe "Baseline Scenario" title summaryDescription = fromMaybe defaultDescription description - pure $ Summary{clusterSize, numberOfTxs, averageConfirmationTime, quantiles, summaryTitle, summaryDescription, numberOfInvalidTxs} + pure $ Summary{clusterSize, totalTxs, numberOfTxs, averageConfirmationTime, quantiles, summaryTitle, summaryDescription, numberOfInvalidTxs} defaultDescription :: Text defaultDescription = "" diff --git a/hydra-cluster/bench/Bench/Summary.hs b/hydra-cluster/bench/Bench/Summary.hs index 28dab93f629..811f0bf57b7 100644 --- a/hydra-cluster/bench/Bench/Summary.hs +++ b/hydra-cluster/bench/Bench/Summary.hs @@ -6,15 +6,18 @@ module Bench.Summary where import Hydra.Prelude import Data.Fixed (Nano) +import Data.Text (pack) import Data.Time (nominalDiffTimeToSeconds) import Data.Vector (Vector, (!)) import Statistics.Quantile (def) import Statistics.Quantile qualified as Statistics +import Text.Printf (printf) type Percent = Double data Summary = Summary { clusterSize :: Word64 + , totalTxs :: Int , numberOfTxs :: Int , numberOfInvalidTxs :: Int , averageConfirmationTime :: NominalDiffTime @@ -30,14 +33,16 @@ makeQuantiles times = Statistics.quantilesVec def (fromList [0 .. 99]) 100 (fromList $ map (fromRational . (* 1000) . toRational . nominalDiffTimeToSeconds) times) textReport :: Summary -> [Text] -textReport Summary{numberOfTxs, averageConfirmationTime, quantiles, numberOfInvalidTxs} = - [ "Confirmed txs: " <> show numberOfTxs - , "Average confirmation time (ms): " <> show (nominalDiffTimeToMilliseconds averageConfirmationTime) - , "P99: " <> show (quantiles ! 99) <> "ms" - , "P95: " <> show (quantiles ! 95) <> "ms" - , "P50: " <> show (quantiles ! 50) <> "ms" - , "Invalid txs: " <> show numberOfInvalidTxs - ] +textReport Summary{totalTxs, numberOfTxs, averageConfirmationTime, quantiles, numberOfInvalidTxs} = + let frac :: Double + frac = 100 * fromIntegral numberOfTxs / fromIntegral totalTxs + in [ pack $ printf "Confirmed txs/Total expected txs: %d/%d (%.2f %%)" numberOfTxs totalTxs frac + , "Average confirmation time (ms): " <> show (nominalDiffTimeToMilliseconds averageConfirmationTime) + , "P99: " <> show (quantiles ! 99) <> "ms" + , "P95: " <> show (quantiles ! 95) <> "ms" + , "P50: " <> show (quantiles ! 50) <> "ms" + , "Invalid txs: " <> show numberOfInvalidTxs + ] markdownReport :: UTCTime -> [Summary] -> [Text] markdownReport now summaries = diff --git a/hydra-cluster/hydra-cluster.cabal b/hydra-cluster/hydra-cluster.cabal index e7124c893bf..2e551403b84 100644 --- a/hydra-cluster/hydra-cluster.cabal +++ b/hydra-cluster/hydra-cluster.cabal @@ -221,6 +221,7 @@ benchmark bench-e2e , regex-tdfa , scientific , statistics + , text , time , vector From e2054432d011978b8a3a49838ea0c49613ee15e8 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Fri, 23 Aug 2024 11:54:23 +0100 Subject: [PATCH 082/115] 4% should work --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 5d2ace810b1..9ef58c706ed 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -88,7 +88,7 @@ jobs: --target 172.16.238.20 \ --target 172.16.238.30 \ loss \ - --percent 75 "re2:hydra-node-1" & + --percent 4 "re2:hydra-node-1" & nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ From b00a43caa05c2784729f72ae5dd490e4bba60cc7 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 26 Aug 2024 11:37:56 +0200 Subject: [PATCH 083/115] Enhance workflow --- .github/workflows/network-test.yaml | 63 ++++++++++------ .github/workflows/network/peers_info_json.sh | 55 ++++++++++++++ .github/workflows/network/run_pumba.sh | 27 +++++++ .github/workflows/network/watch_logs.sh | 77 +++++++++++++++----- hydra-cluster/bench/Bench/Options.hs | 6 +- hydra-cluster/bench/Main.hs | 9 +-- hydra-cluster/src/Hydra/Generator.hs | 13 ++-- 7 files changed, 194 insertions(+), 56 deletions(-) create mode 100755 .github/workflows/network/peers_info_json.sh create mode 100755 .github/workflows/network/run_pumba.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 9ef58c706ed..a6db77a46ce 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -1,7 +1,7 @@ name: "Network fault tolerance" -# TODO: make this a pull_request trigger or so on: + pull_request: workflow_dispatch: inputs: debug_enabled: @@ -9,9 +9,25 @@ on: description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false - push: - branches: - - demo-bench + percent: + type: number + description: 'Percentage for the loss in the netem Pumba command' + required: false + default: 4 + scaling_factor: + type: number + description: 'Scaling factor for the benchmarks' + required: false + default: 10 + target_peer: + description: 'Select the peer to target with netem package loss' + required: true + type: choice + options: + - alice + - bob + - carol + default: alice jobs: network-test: @@ -34,7 +50,6 @@ jobs: name: cardano-scaling authToken: '${{ secrets.CACHIX_CARDANO_SCALING_AUTH_TOKEN }}' - - name: Build docker images for netem specifically run: | nix build .#docker-hydra-node-for-netem @@ -64,37 +79,37 @@ jobs: # # # - # TODO: Use this https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug - # - name: Setup tmate session uses: mxschmitt/action-tmate@v3 - if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} with: limit-access-to-actor: true + - name: Install yq + run: sudo snap install yq + - name: Run pumba and the benchmarks run: | - # TODO: a lot of these parameters could come from workflow arguments! - # Maybe we have good defaults, but then allow people to play around. - # - percent - # - scaling-factor - # - ... - # - nix run github:noonio/pumba/noon/add-flake \ - -- -l debug \ - --random \ - netem \ - --duration 20m \ - --target 172.16.238.20 \ - --target 172.16.238.30 \ - loss \ - --percent 4 "re2:hydra-node-1" & + # Extract inputs with defaults for non-workflow_dispatch events + percent="${{ github.event.inputs.percent || '4' }}" + scaling_factor="${{ github.event.inputs.scaling_factor || '10' }}" + target_peer="${{ github.event.inputs.target_peer || 'alice' }}" + + peers_info_json=$( + .github/workflows/network/peers_info_json.sh demo/docker-compose.yaml + ) + + .github/workflows/network/run_pumba.sh $target_peer $percent $peers_info_json + + .github/workflows/network/watch_logs.sh $target_peer $peers_info_json + # Run benchmark on demo nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ --output-directory=$(pwd)/benchmarks \ - --scaling-factor=10 \ + --scaling-factor="$scaling_factor" \ --timeout=1000s \ + --testnet-magic 42 \ --node-socket=demo/devnet/node.socket \ --hydra-client=localhost:4001 \ --hydra-client=localhost:4002 \ diff --git a/.github/workflows/network/peers_info_json.sh b/.github/workflows/network/peers_info_json.sh new file mode 100755 index 00000000000..6c32cda58a9 --- /dev/null +++ b/.github/workflows/network/peers_info_json.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +if ! command -v yq &> /dev/null || ! command -v jq &> /dev/null +then + echo "yq and jq are required for this script ~ install them." + exit 1 +fi + +compose_file=$1 + +extract_hydra_node_info_json() { + local node_name=$1 + + local port=$(yq eval ".services.$node_name.ports[0]" $compose_file | awk -F: '{print $1}' | tr -d '"') + + local network=$(yq eval ".services.$node_name.networks.hydra_net.ipv4_address" $compose_file | tr -d '"') + + local command_list=$(yq eval ".services.$node_name.command" $compose_file) + + local json=$( + echo "$command_list" | jq -r ' + . as $arr | + reduce range(0; ($arr | length) / 2) as $i ( + {}; + . + {($arr[2 * $i]): $arr[2 * $i + 1]} + ) + | with_entries( + select( + .key == "--node-id" or + .key == "--api-host" or + .key == "--host" or + .key == "--hydra-signing-key" or + .key == "--cardano-signing-key" or + .key == "--ledger-protocol-parameters" or + .key == "--testnet-magic" or + .key == "--persistence-dir" + ) + ) + | with_entries( + .key |= sub("^--"; "") | # Remove -- + .key |= gsub("-"; "_") # Replace - with _ + )' + ) + echo "{\"node_name\": \"$node_name\", \"info\": $json, \"port\": \"$port\", \"network\": \"$network\" }" +} + +peers_info_json() { + echo "{ + \"alice\": $(extract_hydra_node_info_json "hydra-node-1"), + \"bob\": $(extract_hydra_node_info_json "hydra-node-2"), + \"carol\": $(extract_hydra_node_info_json "hydra-node-3") + }" +} + +echo $(peers_info_json) | jq diff --git a/.github/workflows/network/run_pumba.sh b/.github/workflows/network/run_pumba.sh new file mode 100755 index 00000000000..ff652036c7a --- /dev/null +++ b/.github/workflows/network/run_pumba.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +target_peer=$1 + +percent=$2 + +peers_info_json=$3 + +target_node_name=$(echo "$peers_info_json" | jq -r ".$target_peer.node_name") + +# Build Pumba netem command +unselected_networks=$(echo "$peers_info_json" | jq -r --arg selected_peer "$target_peer" ' + to_entries[] | select(.key != $selected_peer) | .value.network +') + +nix_command="nix run github:noonio/pumba/noon/add-flake -- -l debug --random netem --duration 20m" + +while IFS= read -r network; do + nix_command+=" --target $network" +done <<< "$unselected_networks" + +nix_command+=" loss --percent \"$percent\" \"re2:$target_node_name\" &" + +echo "$nix_command" + +# Run Pumba netem command +eval "$nix_command" \ No newline at end of file diff --git a/.github/workflows/network/watch_logs.sh b/.github/workflows/network/watch_logs.sh index f38e9dc3873..2e130eb0d67 100755 --- a/.github/workflows/network/watch_logs.sh +++ b/.github/workflows/network/watch_logs.sh @@ -1,34 +1,73 @@ #!/usr/bin/env bash -set -ex +target_peer=$1 -LOG_FILE_BOB="demo/devnet/persistence/bob/server-output" -LOG_FILE_CAROL="demo/devnet/persistence/carol/server-output" -JQ_EXPRESSION='select(.tag == "PeerDisconnected" and .peer == "1")' +peers_info_json=$2 + +peers_to_watch_json=$(echo "$peers_info_json" | jq -r --arg selected_peer "$target_peer" ' + to_entries + | map(select(.key != $selected_peer)) + | map(.key) +') + +logs_file_json=$(echo "$peers_info_json" | jq --arg selected_peer "$target_peer" ' + to_entries + | map(select(.key != $selected_peer)) + | map({ + (.key): (.value.info.persistence_dir | sub("^/"; "")) + }) + | add +') + +target_peer_id=$(echo "$peers_info_json" \ + | jq -r --arg selected_peer "$target_peer" '.[$selected_peer].info.node_id') check_log() { - local log_file=$1 - jq "$JQ_EXPRESSION" "$log_file" | grep -q . + local peer=$1 + local peer_log_file=$(echo "$logs_file_json" | jq -r --arg peer "$peer" '.[$peer]') + if jq --arg target_peer_id $target_peer_id \ + 'select(.tag == "PeerDisconnected" and .peer == $target_peer_id)' \ + "demo/$peer_log_file/server-output" | grep -q .; then + echo "Success for peer: $peer" + return 0 # Success: log file has output + else + echo "Failure for peer: $peer" + return 1 # Failure: log file is empty or doesn't exist + fi } -bob_ready=false -carol_ready=false +declare -A peer_ready +for peer in $(echo "$peers_to_watch_json" | jq -r '.[]'); do + peer_ready[$peer]=false +done while true; do - if ! $bob_ready && check_log "$LOG_FILE_BOB"; then - echo "Match found in Bob's log file!" - bob_ready=true - fi + # Assume all are ready until proven otherwise + all_ready=true - if ! $carol_ready && check_log "$LOG_FILE_CAROL"; then - echo "Match found in Carol's log file!" - carol_ready=true - fi + for peer in "${!peer_ready[@]}"; do + echo "Checking $peer..." + + if [[ "${peer_ready[$peer]}" == false ]]; then + check_log "$peer" + # Check the exit status of the check_log function + if [[ $? -eq 0 ]]; then + echo "$peer is now marked as ready." + peer_ready[$peer]=true + fi + fi - if $bob_ready && $carol_ready; then - echo "Both conditions met!" + # Check if the current peer is not ready + if [[ "${peer_ready[$peer]}" == false ]]; then + # If any peer is not ready, set all_ready to false + all_ready=false + fi + done + + if [[ "$all_ready" == true ]]; then + echo "All peers are ready!" break fi sleep 5 -done +done \ No newline at end of file diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index bbf17e1e9eb..d883d77d6a0 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -2,10 +2,10 @@ module Bench.Options where import Hydra.Prelude -import Hydra.Cardano.Api (SocketPath) +import Hydra.Cardano.Api (NetworkId, SocketPath) import Hydra.Chain (maximumNumberOfParties) import Hydra.Network (Host, readHost) -import Hydra.Options (nodeSocketParser) +import Hydra.Options (networkIdParser, nodeSocketParser) import Options.Applicative (Parser, ParserInfo, auto, command, fullDesc, header, help, helpDoc, helper, hsubparser, info, long, maybeReader, metavar, option, progDesc, short, str, strOption, value) import Options.Applicative.Builder (argument) import Options.Applicative.Help (Doc, align, fillSep, line, (<+>)) @@ -29,6 +29,7 @@ data Options { outputDirectory :: Maybe FilePath , scalingFactor :: Int , timeoutSeconds :: NominalDiffTime + , networkId :: NetworkId , nodeSocket :: SocketPath , hydraClients :: [Host] } @@ -165,6 +166,7 @@ demoOptionsParser = <$> optional outputDirectoryParser <*> scalingFactorParser <*> timeoutParser + <*> networkIdParser <*> nodeSocketParser <*> many hydraClientsParser diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index a84230203a5..b631dea11b8 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -9,7 +9,7 @@ import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) import Bench.Summary (Summary (..), markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) -import Hydra.Cluster.Fixture (Actor (..), defaultNetworkId) +import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Util (keysFor) import Hydra.Generator (ClientKeys (..), Dataset (..), generateConstantUTxODataset, generateDemoUTxODataset) import Options.Applicative (execParser) @@ -37,8 +37,8 @@ main = do DatasetOptions{datasetFiles, outputDirectory, timeoutSeconds, startingNodeId} -> do let action = bench startingNodeId timeoutSeconds run outputDirectory datasetFiles action - DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, nodeSocket, hydraClients} -> do - let action = benchDemo defaultNetworkId nodeSocket timeoutSeconds hydraClients + DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket, hydraClients} -> do + let action = benchDemo networkId nodeSocket timeoutSeconds hydraClients -- TODO: Maybe all this should be moved down somewhere. numberOfTxs <- generate $ scale (* scalingFactor) getSize let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] @@ -47,8 +47,7 @@ main = do fundsSk <- snd <$> keysFor funds pure $ ClientKeys sk fundsSk clientKeys <- forM actors toClientKeys - - dataset <- generateDemoUTxODataset nodeSocket clientKeys numberOfTxs + dataset <- generateDemoUTxODataset networkId nodeSocket clientKeys numberOfTxs withTempDir "bench-demo" $ runSingle outputDirectory dataset action where diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index b3727ac3bf2..35f7a8dc3b9 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -122,15 +122,16 @@ generateConstantUTxODataset faucetSk nClients nTxs = do -- TODO: Refactor generateDemoUTxODataset :: + NetworkId -> SocketPath -> -- | Number of clients [ClientKeys] -> -- | Number of transactions Int -> IO Dataset -generateDemoUTxODataset nodeSocket allClientKeys nTxs = do +generateDemoUTxODataset network nodeSocket allClientKeys nTxs = do (faucetVk, faucetSk) <- keysFor Faucet - faucetUTxO <- queryUTxOFor networkId nodeSocket QueryTip faucetVk + faucetUTxO <- queryUTxOFor network nodeSocket QueryTip faucetVk let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) let nClients = length allClientKeys -- Prepare funding transaction which will give every client's @@ -142,13 +143,13 @@ generateDemoUTxODataset nodeSocket allClientKeys nTxs = do let recipientOutputs = flip map clientFunds $ \(vk, ll) -> TxOut - (mkVkAddress networkId vk) + (mkVkAddress network vk) (lovelaceToValue ll) TxOutDatumNone ReferenceScriptNone - let changeAddress = mkVkAddress networkId faucetVk + let changeAddress = mkVkAddress network faucetVk fundingTransaction <- - buildTransaction networkId nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case + buildTransaction network nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign faucetSk body @@ -162,7 +163,7 @@ generateDemoUTxODataset nodeSocket allClientKeys nTxs = do txSequence <- reverse . thrd - <$> foldM (generateOneSelfTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] + <$> foldM (generateOneSelfTransfer network) (initialUTxO, externalSigningKey, []) [1 .. nTxs] pure ClientDataset{clientKeys, initialUTxO, txSequence} -- * Helpers From 0872fa131c11b2033a3268218aae5b81998cf107 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 27 Aug 2024 11:44:38 +0200 Subject: [PATCH 084/115] Some nice refactoring --- .github/workflows/network-test.yaml | 2 +- .github/workflows/network/peers_info_json.sh | 9 ++- hydra-cluster/bench/Main.hs | 41 +++++----- hydra-cluster/src/Hydra/Generator.hs | 81 +++++++++++--------- 4 files changed, 73 insertions(+), 60 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index a6db77a46ce..8763a5b3d85 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -91,7 +91,7 @@ jobs: - name: Run pumba and the benchmarks run: | # Extract inputs with defaults for non-workflow_dispatch events - percent="${{ github.event.inputs.percent || '4' }}" + percent="${{ github.event.inputs.percent || '40' }}" scaling_factor="${{ github.event.inputs.scaling_factor || '10' }}" target_peer="${{ github.event.inputs.target_peer || 'alice' }}" diff --git a/.github/workflows/network/peers_info_json.sh b/.github/workflows/network/peers_info_json.sh index 6c32cda58a9..bbc1dcf43bc 100755 --- a/.github/workflows/network/peers_info_json.sh +++ b/.github/workflows/network/peers_info_json.sh @@ -52,4 +52,11 @@ peers_info_json() { }" } -echo $(peers_info_json) | jq +json_result=$(echo $(peers_info_json) | jq -c) + +if [[ -z "$json_result" ]]; then + echo "Error: json_result is empty." + exit 1 +fi + +echo $json_result diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index b631dea11b8..eb26528ca56 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -39,18 +39,21 @@ main = do run outputDirectory datasetFiles action DemoOptions{outputDirectory, scalingFactor, timeoutSeconds, networkId, nodeSocket, hydraClients} -> do let action = benchDemo networkId nodeSocket timeoutSeconds hydraClients - -- TODO: Maybe all this should be moved down somewhere. - numberOfTxs <- generate $ scale (* scalingFactor) getSize - let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] - let toClientKeys (actor, funds) = do - sk <- snd <$> keysFor actor - fundsSk <- snd <$> keysFor funds - pure $ ClientKeys sk fundsSk - clientKeys <- forM actors toClientKeys - dataset <- generateDemoUTxODataset networkId nodeSocket clientKeys numberOfTxs - withTempDir "bench-demo" $ - runSingle outputDirectory dataset action + playDemo outputDirectory scalingFactor networkId nodeSocket action where + playDemo outputDirectory scalingFactor networkId nodeSocket action = do + numberOfTxs <- generate $ scale (* scalingFactor) getSize + let actors = [(Alice, AliceFunds), (Bob, BobFunds), (Carol, CarolFunds)] + let toClientKeys (actor, funds) = do + sk <- snd <$> keysFor actor + fundsSk <- snd <$> keysFor funds + pure $ ClientKeys sk fundsSk + clientKeys <- forM actors toClientKeys + dataset <- generateDemoUTxODataset networkId nodeSocket clientKeys numberOfTxs + results <- withTempDir "bench-demo" $ \dir -> do + runSingle dataset action dir + summarizeResults outputDirectory [results] + play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do (_, faucetSk) <- keysFor Faucet putStrLn $ "Generating single dataset in work directory: " <> workDir @@ -67,8 +70,7 @@ main = do let action = bench startingNodeId timeoutSeconds run outputDirectory [datasetPath] action - -- TODO: Needs a bit of a refactor. - runSingle' dataset action dir = do + runSingle dataset action dir = do withArgs [] $ do try @_ @HUnitFailure (action dir dataset) >>= \case Left exc -> pure $ Left (dataset, dir, TestFailed exc) @@ -76,14 +78,6 @@ main = do | numberOfInvalidTxs == 0 -> pure $ Right summary | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) - runSingle outputDirectory dataset action dir = do - results <- runSingle' dataset action dir - let (failures, summaries) = partitionEithers [results] - -- TODO: Duplicated below; needs to be refactored. - case failures of - [] -> benchmarkSucceeded outputDirectory summaries - errs -> mapM_ (\(_, d, exc) -> benchmarkFailedWith d exc) errs >> exitFailure - run outputDirectory datasetFiles action = do results <- forM datasetFiles $ \datasetPath -> do putTextLn $ "Running benchmark with dataset " <> show datasetPath @@ -91,7 +85,10 @@ main = do withTempDir ("bench-" <> takeFileName datasetPath) $ \dir -> do -- XXX: Wait between each bench run to give the OS time to cleanup resources?? threadDelay 10 - runSingle' dataset action dir + runSingle dataset action dir + summarizeResults outputDirectory results + + summarizeResults outputDirectory results = do let (failures, summaries) = partitionEithers results case failures of [] -> benchmarkSucceeded outputDirectory summaries diff --git a/hydra-cluster/src/Hydra/Generator.hs b/hydra-cluster/src/Hydra/Generator.hs index 35f7a8dc3b9..04ac56eb503 100644 --- a/hydra-cluster/src/Hydra/Generator.hs +++ b/hydra-cluster/src/Hydra/Generator.hs @@ -90,13 +90,11 @@ generateConstantUTxODataset :: Int -> Gen Dataset generateConstantUTxODataset faucetSk nClients nTxs = do - clientKeys <- replicateM nClients arbitrary + allClientKeys <- replicateM nClients arbitrary -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the benchmark run. - clientFunds <- forM clientKeys $ \ClientKeys{externalSigningKey} -> do - amount <- Coin <$> choose (1, availableInitialFunds `div` fromIntegral nClients) - pure (getVerificationKey externalSigningKey, amount) + clientFunds <- genClientFunds allClientKeys availableInitialFunds let fundingTransaction = buildRawTransaction networkId @@ -104,7 +102,9 @@ generateConstantUTxODataset faucetSk nClients nTxs = do faucetSk (Coin availableInitialFunds) clientFunds - clientDatasets <- forM clientKeys (generateClientDataset fundingTransaction) + let dataset clientKeys = + generateClientDataset networkId fundingTransaction clientKeys nTxs generateOneRandomTransfer + clientDatasets <- forM allClientKeys dataset pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} where initialInput = @@ -112,15 +112,10 @@ generateConstantUTxODataset faucetSk nClients nTxs = do networkId (unsafeCastHash $ verificationKeyHash $ getVerificationKey faucetSk) - generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do - let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction - txSequence <- - reverse - . thrd - <$> foldM (generateOneRandomTransfer networkId) (initialUTxO, externalSigningKey, []) [1 .. nTxs] - pure ClientDataset{clientKeys, initialUTxO, txSequence} - --- TODO: Refactor +-- | Generate 'Dataset' which does not grow the per-client UTXO set over time. +-- This queries the network to fetch the current funds available in the faucet +-- to be distributed among the peers. +-- The sequence of transactions generated consist only of simple self payments. generateDemoUTxODataset :: NetworkId -> SocketPath -> @@ -133,38 +128,29 @@ generateDemoUTxODataset network nodeSocket allClientKeys nTxs = do (faucetVk, faucetSk) <- keysFor Faucet faucetUTxO <- queryUTxOFor network nodeSocket QueryTip faucetVk let (Coin fundsAvailable) = selectLovelace (balance @Tx faucetUTxO) - let nClients = length allClientKeys -- Prepare funding transaction which will give every client's -- 'externalSigningKey' "some" lovelace. The internal 'signingKey' will get -- funded in the beginning of the benchmark run. - clientFunds <- forM allClientKeys $ \ClientKeys{externalSigningKey} -> do - amount <- generate $ Coin <$> choose (1, fundsAvailable `div` fromIntegral nClients) - pure (getVerificationKey externalSigningKey, amount) - let recipientOutputs = - flip map clientFunds $ \(vk, ll) -> - TxOut - (mkVkAddress network vk) - (lovelaceToValue ll) - TxOutDatumNone - ReferenceScriptNone - let changeAddress = mkVkAddress network faucetVk - fundingTransaction <- + clientFunds <- generate $ genClientFunds allClientKeys fundsAvailable + fundingTransaction <- do + let changeAddress = mkVkAddress network faucetVk + let recipientOutputs = + flip map clientFunds $ \(vk, ll) -> + TxOut + (mkVkAddress network vk) + (lovelaceToValue ll) + TxOutDatumNone + ReferenceScriptNone buildTransaction network nodeSocket changeAddress faucetUTxO [] recipientOutputs >>= \case Left e -> throwIO $ FaucetFailedToBuildTx{reason = e} Right body -> do let signedTx = sign faucetSk body pure signedTx + let dataset clientKeys = + generateClientDataset network fundingTransaction clientKeys nTxs generateOneSelfTransfer generate $ do - clientDatasets <- forM allClientKeys (generateClientDataset fundingTransaction) + clientDatasets <- forM allClientKeys dataset pure Dataset{fundingTransaction, clientDatasets, title = Nothing, description = Nothing} - where - generateClientDataset fundingTransaction clientKeys@ClientKeys{externalSigningKey} = do - let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction - txSequence <- - reverse - . thrd - <$> foldM (generateOneSelfTransfer network) (initialUTxO, externalSigningKey, []) [1 .. nTxs] - pure ClientDataset{clientKeys, initialUTxO, txSequence} -- * Helpers thrd :: (a, b, c) -> c @@ -178,3 +164,26 @@ withInitialUTxO externalSigningKey fundingTransaction = -- into the head. utxoProducedByTx fundingTransaction & UTxO.filter ((== mkVkAddress networkId vk) . txOutAddress) + +genClientFunds :: [ClientKeys] -> Integer -> Gen [(VerificationKey PaymentKey, Coin)] +genClientFunds clientKeys availableFunds = + forM clientKeys $ \ClientKeys{externalSigningKey} -> do + amount <- Coin <$> choose (1, availableFunds `div` fromIntegral nClients) + pure (getVerificationKey externalSigningKey, amount) + where + nClients = length clientKeys + +generateClientDataset :: + NetworkId -> + Tx -> + ClientKeys -> + Int -> + (NetworkId -> (UTxO, SigningKey PaymentKey, [Tx]) -> Int -> Gen (UTxO, SigningKey PaymentKey, [Tx])) -> + Gen ClientDataset +generateClientDataset network fundingTransaction clientKeys@ClientKeys{externalSigningKey} nTxs action = do + let initialUTxO = withInitialUTxO externalSigningKey fundingTransaction + txSequence <- + reverse + . thrd + <$> foldM (action network) (initialUTxO, externalSigningKey, []) [1 .. nTxs] + pure ClientDataset{clientKeys, initialUTxO, txSequence} From 8e2c14d10d2a1dd4f319a7fcc19a2fd838a5e3ad Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 27 Aug 2024 12:59:41 +0200 Subject: [PATCH 085/115] Make watch for logs optional --- .github/workflows/network-test.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 8763a5b3d85..1853fef422a 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -19,6 +19,11 @@ on: description: 'Scaling factor for the benchmarks' required: false default: 10 + wait_peer_dc_enabled: + type: boolean + description: 'Check other peers see the target peer is disconnected before running benchmark.' + required: false + default: false target_peer: description: 'Select the peer to target with netem package loss' required: true @@ -91,7 +96,7 @@ jobs: - name: Run pumba and the benchmarks run: | # Extract inputs with defaults for non-workflow_dispatch events - percent="${{ github.event.inputs.percent || '40' }}" + percent="${{ github.event.inputs.percent || '4' }}" scaling_factor="${{ github.event.inputs.scaling_factor || '10' }}" target_peer="${{ github.event.inputs.target_peer || 'alice' }}" @@ -101,7 +106,11 @@ jobs: .github/workflows/network/run_pumba.sh $target_peer $percent $peers_info_json - .github/workflows/network/watch_logs.sh $target_peer $peers_info_json + wait_peer_dc_enabled="${{ github.event.inputs.wait_peer_dc_enabled || false }}" + + if [[ "$wait_peer_dc_enabled" == "false" ]]; then + .github/workflows/network/watch_logs.sh $target_peer $peers_info_json + fi # Run benchmark on demo nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ From f77ef9b77de14b9c76164dffacdda5370a49782f Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 27 Aug 2024 13:03:46 +0100 Subject: [PATCH 086/115] Reframe wait for peer disconnected variable --- .github/workflows/network-test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 1853fef422a..268ebe59764 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -19,7 +19,7 @@ on: description: 'Scaling factor for the benchmarks' required: false default: 10 - wait_peer_dc_enabled: + wait_peer_disconnected: type: boolean description: 'Check other peers see the target peer is disconnected before running benchmark.' required: false @@ -106,9 +106,9 @@ jobs: .github/workflows/network/run_pumba.sh $target_peer $percent $peers_info_json - wait_peer_dc_enabled="${{ github.event.inputs.wait_peer_dc_enabled || false }}" + wait_peer_disconnected="${{ github.event.inputs.wait_peer_disconnected || false }}" - if [[ "$wait_peer_dc_enabled" == "false" ]]; then + if [[ "$wait_peer_disconnected" == "true" ]]; then .github/workflows/network/watch_logs.sh $target_peer $peers_info_json fi From a8550212aa34873333edc1b051792e955cb0b884 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 27 Aug 2024 13:14:44 +0100 Subject: [PATCH 087/115] Experiment with a matrix --- .github/workflows/network-test.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 268ebe59764..0e83ed53b18 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -37,6 +37,9 @@ on: jobs: network-test: runs-on: ubuntu-latest + strategy: + matrix: + netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] steps: - uses: actions/checkout@v4 with: @@ -96,7 +99,7 @@ jobs: - name: Run pumba and the benchmarks run: | # Extract inputs with defaults for non-workflow_dispatch events - percent="${{ github.event.inputs.percent || '4' }}" + percent="${{ matrix.netem_loss }}" scaling_factor="${{ github.event.inputs.scaling_factor || '10' }}" target_peer="${{ github.event.inputs.target_peer || 'alice' }}" From faee8b894d42129efade3c90cb1df6f60d2b07e8 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 27 Aug 2024 13:21:36 +0100 Subject: [PATCH 088/115] Name in artifact --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 0e83ed53b18..72129a1b82f 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -137,6 +137,6 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: docker-logs + name: "docker-logs-netem-loss=${{ matrix.netem_loss }}" path: demo/docker-logs if-no-files-found: ignore From 0efbdc4f3501eb19c5fb07953966d93a87265061 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 27 Aug 2024 13:33:58 +0100 Subject: [PATCH 089/115] Expect-failure matrix configuration --- .github/workflows/network-test.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 72129a1b82f..c05ce0827f0 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -9,11 +9,6 @@ on: description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false - percent: - type: number - description: 'Percentage for the loss in the netem Pumba command' - required: false - default: 4 scaling_factor: type: number description: 'Scaling factor for the benchmarks' @@ -39,7 +34,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] + netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] + expect_failure: [false] + include: + - netem_loss: 10 + expect_failure: true + - netem_loss: 20 + expect_failure: true steps: - uses: actions/checkout@v4 with: @@ -97,6 +98,7 @@ jobs: run: sudo snap install yq - name: Run pumba and the benchmarks + continue-on-error: ${{ matrix.expect_failure }} run: | # Extract inputs with defaults for non-workflow_dispatch events percent="${{ matrix.netem_loss }}" From 7335112199736d29159a21938accc17d57f92ac0 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 27 Aug 2024 13:44:14 +0100 Subject: [PATCH 090/115] Add failure mode we expect; refine matrix --- .github/workflows/network-test.yaml | 7 +++++++ hydra-cluster/bench/Main.hs | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index c05ce0827f0..595a772d058 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -36,6 +36,13 @@ jobs: matrix: netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] expect_failure: [false] + # We know these will fail; so don't try. + exclude: + - netem_loss: 10 + expect_failure: false + - netem_loss: 20 + expect_failure: false + # In fact, allow them to fail. include: - netem_loss: 10 expect_failure: true diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index eb26528ca56..c96769dd791 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -74,8 +74,9 @@ main = do withArgs [] $ do try @_ @HUnitFailure (action dir dataset) >>= \case Left exc -> pure $ Left (dataset, dir, TestFailed exc) - Right summary@Summary{numberOfInvalidTxs} + Right summary@Summary{totalTxs,numberOfTxs,numberOfInvalidTxs} | numberOfInvalidTxs == 0 -> pure $ Right summary + | numberOfTxs /= totalTxs -> pure $ Left (dataset, dir, NotEnoughTransactions numberOfTxs totalTxs) | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) run outputDirectory datasetFiles action = do @@ -108,12 +109,15 @@ main = do data BenchmarkFailed = TestFailed HUnitFailure | InvalidTransactions Int + | NotEnoughTransactions Int Int benchmarkFailedWith :: FilePath -> BenchmarkFailed -> IO () benchmarkFailedWith benchDir = \case (TestFailed (HUnitFailure sourceLocation reason)) -> do putStrLn $ "Benchmark failed " <> formatLocation sourceLocation <> ": " <> formatFailureReason reason putStrLn $ "To re-run with same dataset, pass '--work-directory=" <> benchDir <> "' to the executable" + (NotEnoughTransactions actual expected) -> do + putStrLn $ "Benchmark resulted in " <> show actual <> " transactions; but wanted " <> show expected <> "." (InvalidTransactions n) -> do putStrLn $ "Benchmark has " <> show n <> " invalid transactions" putStrLn $ From c80281f24ee76da08473361125811b5fd789eaaa Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 27 Aug 2024 14:08:22 +0100 Subject: [PATCH 091/115] Fix order of detecting error conditions --- hydra-cluster/bench/Main.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index c96769dd791..5f86eef6069 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -75,8 +75,8 @@ main = do try @_ @HUnitFailure (action dir dataset) >>= \case Left exc -> pure $ Left (dataset, dir, TestFailed exc) Right summary@Summary{totalTxs,numberOfTxs,numberOfInvalidTxs} - | numberOfInvalidTxs == 0 -> pure $ Right summary | numberOfTxs /= totalTxs -> pure $ Left (dataset, dir, NotEnoughTransactions numberOfTxs totalTxs) + | numberOfInvalidTxs == 0 -> pure $ Right summary | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) run outputDirectory datasetFiles action = do From a1e0de463d3e91d4f0363073f0f8c3c31a1663b6 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Tue, 27 Aug 2024 14:25:39 +0100 Subject: [PATCH 092/115] Add scaling factor to matrix config --- .github/workflows/network-test.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 595a772d058..69e7946ac2c 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -9,11 +9,6 @@ on: description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false - scaling_factor: - type: number - description: 'Scaling factor for the benchmarks' - required: false - default: 10 wait_peer_disconnected: type: boolean description: 'Check other peers see the target peer is disconnected before running benchmark.' @@ -34,19 +29,24 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + scaling_factor: [10] netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] expect_failure: [false] # We know these will fail; so don't try. exclude: - - netem_loss: 10 + - scaling_factor: 10 + netem_loss: 10 expect_failure: false - - netem_loss: 20 + - scaling_factor: 10 + netem_loss: 20 expect_failure: false # In fact, allow them to fail. include: - - netem_loss: 10 + - scaling_factor: 10 + netem_loss: 10 expect_failure: true - - netem_loss: 20 + - scaling_factor: 10 + netem_loss: 20 expect_failure: true steps: - uses: actions/checkout@v4 @@ -109,7 +109,7 @@ jobs: run: | # Extract inputs with defaults for non-workflow_dispatch events percent="${{ matrix.netem_loss }}" - scaling_factor="${{ github.event.inputs.scaling_factor || '10' }}" + scaling_factor="${{ matrix.scaling_factor }}" target_peer="${{ github.event.inputs.target_peer || 'alice' }}" peers_info_json=$( @@ -146,6 +146,6 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: "docker-logs-netem-loss=${{ matrix.netem_loss }}" + name: "docker-logs-netem-loss=${{ matrix.netem_loss }},scaling_factor=${{ matrix.scaling_factor }}" path: demo/docker-logs if-no-files-found: ignore From d14576b7dfc51824782f8ad72972f038d86ec922 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 27 Aug 2024 18:21:42 +0200 Subject: [PATCH 093/115] Replace yq by docker inspect --- .github/workflows/network-test.yaml | 7 +------ .github/workflows/network/peers_info_json.sh | 14 ++++++-------- .github/workflows/network/run_pumba.sh | 6 ++++++ .github/workflows/network/watch_logs.sh | 6 ++++++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 69e7946ac2c..468c3c699ee 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -101,9 +101,6 @@ jobs: with: limit-access-to-actor: true - - name: Install yq - run: sudo snap install yq - - name: Run pumba and the benchmarks continue-on-error: ${{ matrix.expect_failure }} run: | @@ -112,9 +109,7 @@ jobs: scaling_factor="${{ matrix.scaling_factor }}" target_peer="${{ github.event.inputs.target_peer || 'alice' }}" - peers_info_json=$( - .github/workflows/network/peers_info_json.sh demo/docker-compose.yaml - ) + peers_info_json=$(.github/workflows/network/peers_info_json.sh) .github/workflows/network/run_pumba.sh $target_peer $percent $peers_info_json diff --git a/.github/workflows/network/peers_info_json.sh b/.github/workflows/network/peers_info_json.sh index bbc1dcf43bc..8e1936242eb 100755 --- a/.github/workflows/network/peers_info_json.sh +++ b/.github/workflows/network/peers_info_json.sh @@ -1,21 +1,19 @@ #!/usr/bin/env bash -if ! command -v yq &> /dev/null || ! command -v jq &> /dev/null +if ! command -v jq &> /dev/null then - echo "yq and jq are required for this script ~ install them." + echo "jq is required for this script ~ install it." exit 1 fi -compose_file=$1 - extract_hydra_node_info_json() { local node_name=$1 - local port=$(yq eval ".services.$node_name.ports[0]" $compose_file | awk -F: '{print $1}' | tr -d '"') + local container_name="demo-$node_name-1" - local network=$(yq eval ".services.$node_name.networks.hydra_net.ipv4_address" $compose_file | tr -d '"') + local network=$(docker inspect "$container_name" | jq '.[0].NetworkSettings.Networks.demo_hydra_net.IPAMConfig.IPv4Address' | tr -d '"') - local command_list=$(yq eval ".services.$node_name.command" $compose_file) + local command_list=$(docker inspect "$container_name" | jq '.[0].Config.Cmd') local json=$( echo "$command_list" | jq -r ' @@ -41,7 +39,7 @@ extract_hydra_node_info_json() { .key |= gsub("-"; "_") # Replace - with _ )' ) - echo "{\"node_name\": \"$node_name\", \"info\": $json, \"port\": \"$port\", \"network\": \"$network\" }" + echo "{\"node_name\": \"$node_name\", \"info\": $json, \"network\": \"$network\" }" } peers_info_json() { diff --git a/.github/workflows/network/run_pumba.sh b/.github/workflows/network/run_pumba.sh index ff652036c7a..94fc2f85504 100755 --- a/.github/workflows/network/run_pumba.sh +++ b/.github/workflows/network/run_pumba.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +if ! command -v jq &> /dev/null +then + echo "jq is required for this script ~ install it." + exit 1 +fi + target_peer=$1 percent=$2 diff --git a/.github/workflows/network/watch_logs.sh b/.github/workflows/network/watch_logs.sh index 2e130eb0d67..fac7fdfc185 100755 --- a/.github/workflows/network/watch_logs.sh +++ b/.github/workflows/network/watch_logs.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +if ! command -v jq &> /dev/null +then + echo "jq is required for this script ~ install it." + exit 1 +fi + target_peer=$1 peers_info_json=$2 From b90ee871bedb708b1e7e0727d17081505325ada1 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 27 Aug 2024 18:31:06 +0200 Subject: [PATCH 094/115] Upload the results as part of the artifacts --- .github/workflows/network-test.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 468c3c699ee..2ef9b99dccf 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -122,7 +122,7 @@ jobs: # Run benchmark on demo nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ - --output-directory=$(pwd)/benchmarks \ + --output-directory=benchmarks \ --scaling-factor="$scaling_factor" \ --timeout=1000s \ --testnet-magic 42 \ @@ -144,3 +144,11 @@ jobs: name: "docker-logs-netem-loss=${{ matrix.netem_loss }},scaling_factor=${{ matrix.scaling_factor }}" path: demo/docker-logs if-no-files-found: ignore + + - name: 💾 Upload build & test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: "benchmarks-netem-loss=${{ matrix.netem_loss }},scaling_factor=${{ matrix.scaling_factor }}" + path: benchmarks + if-no-files-found: ignore \ No newline at end of file From 46746c6508f9ffb5a240b5c8ee3770dd3e82e035 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 27 Aug 2024 19:01:54 +0200 Subject: [PATCH 095/115] Make sure results.csv is written to the outputDirectory not the tmp directory --- hydra-cluster/bench/Bench/EndToEnd.hs | 1 + hydra-cluster/bench/Main.hs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 1e0a4d9f63c..f42b511d87b 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -154,6 +154,7 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas traceWith faucetTracer $ ReturnedFunds{actor = show sender, returnAmount} ) senders + scenario :: Tracer IO HydraNodeLog -> RunningNode -> diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 5f86eef6069..99925a443ec 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -51,7 +51,7 @@ main = do clientKeys <- forM actors toClientKeys dataset <- generateDemoUTxODataset networkId nodeSocket clientKeys numberOfTxs results <- withTempDir "bench-demo" $ \dir -> do - runSingle dataset action dir + runSingle dataset action (fromMaybe dir outputDirectory) summarizeResults outputDirectory [results] play outputDirectory timeoutSeconds scalingFactor clusterSize startingNodeId workDir = do @@ -74,7 +74,7 @@ main = do withArgs [] $ do try @_ @HUnitFailure (action dir dataset) >>= \case Left exc -> pure $ Left (dataset, dir, TestFailed exc) - Right summary@Summary{totalTxs,numberOfTxs,numberOfInvalidTxs} + Right summary@Summary{totalTxs, numberOfTxs, numberOfInvalidTxs} | numberOfTxs /= totalTxs -> pure $ Left (dataset, dir, NotEnoughTransactions numberOfTxs totalTxs) | numberOfInvalidTxs == 0 -> pure $ Right summary | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) From 8324d53fa7be193fee392aac62df3e418da6cc3e Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Tue, 27 Aug 2024 19:54:43 +0200 Subject: [PATCH 096/115] Write the summary out even when it failed --- .github/workflows/network-test.yaml | 2 ++ hydra-cluster/bench/Bench/EndToEnd.hs | 12 +++++++++++- hydra-cluster/bench/Bench/Summary.hs | 19 +++++++++++++++++++ hydra-cluster/bench/Main.hs | 26 +++++++++++++++++--------- 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 2ef9b99dccf..f95cf7c926c 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -120,6 +120,8 @@ jobs: fi # Run benchmark on demo + mkdir benchmarks + touch benchmarks/test.log nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ --output-directory=benchmarks \ diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index f42b511d87b..ab91956fc28 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -218,7 +218,17 @@ scenario hydraTracer node workDir Dataset{clientDatasets, title, description} pa summaryTitle = fromMaybe "Baseline Scenario" title summaryDescription = fromMaybe defaultDescription description - pure $ Summary{clusterSize, totalTxs, numberOfTxs, averageConfirmationTime, quantiles, summaryTitle, summaryDescription, numberOfInvalidTxs} + pure $ + Summary + { clusterSize + , totalTxs + , numberOfTxs + , averageConfirmationTime + , quantiles + , summaryTitle + , summaryDescription + , numberOfInvalidTxs + } defaultDescription :: Text defaultDescription = "" diff --git a/hydra-cluster/bench/Bench/Summary.hs b/hydra-cluster/bench/Bench/Summary.hs index 811f0bf57b7..f17700440e4 100644 --- a/hydra-cluster/bench/Bench/Summary.hs +++ b/hydra-cluster/bench/Bench/Summary.hs @@ -9,8 +9,11 @@ import Data.Fixed (Nano) import Data.Text (pack) import Data.Time (nominalDiffTimeToSeconds) import Data.Vector (Vector, (!)) +import Hydra.Generator (ClientDataset (..), Dataset (..)) import Statistics.Quantile (def) import Statistics.Quantile qualified as Statistics +import Test.HUnit.Lang (formatFailureReason) +import Test.Hydra.Prelude (HUnitFailure (..)) import Text.Printf (printf) type Percent = Double @@ -28,6 +31,22 @@ data Summary = Summary deriving stock (Generic, Eq, Show) deriving anyclass (ToJSON) +errorSummary :: Dataset -> HUnitFailure -> Summary +errorSummary Dataset{title, clientDatasets} (HUnitFailure sourceLocation reason) = + Summary + { clusterSize = fromIntegral $ length clientDatasets + , totalTxs = length $ foldMap (\ClientDataset{txSequence} -> txSequence) clientDatasets + , numberOfTxs = 0 + , numberOfInvalidTxs = 0 + , averageConfirmationTime = 0 + , summaryTitle = maybe "Error Summary" ("Error Summary " <>) title + , summaryDescription = + pack $ "Benchmark failed " <> formatLocation sourceLocation <> ": " <> formatFailureReason reason + , quantiles = mempty + } + where + formatLocation = maybe "" (\loc -> "at " <> prettySrcLoc loc) + makeQuantiles :: [NominalDiffTime] -> Vector Double makeQuantiles times = Statistics.quantilesVec def (fromList [0 .. 99]) 100 (fromList $ map (fromRational . (* 1000) . toRational . nominalDiffTimeToSeconds) times) diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 99925a443ec..97fd30be957 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -7,7 +7,7 @@ import Test.Hydra.Prelude import Bench.EndToEnd (bench, benchDemo) import Bench.Options (Options (..), benchOptionsParser) -import Bench.Summary (Summary (..), markdownReport, textReport) +import Bench.Summary (Summary (..), errorSummary, markdownReport, textReport) import Data.Aeson (eitherDecodeFileStrict', encodeFile) import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Util (keysFor) @@ -73,11 +73,11 @@ main = do runSingle dataset action dir = do withArgs [] $ do try @_ @HUnitFailure (action dir dataset) >>= \case - Left exc -> pure $ Left (dataset, dir, TestFailed exc) + Left exc -> pure $ Left (dataset, dir, errorSummary dataset exc, TestFailed exc) Right summary@Summary{totalTxs, numberOfTxs, numberOfInvalidTxs} - | numberOfTxs /= totalTxs -> pure $ Left (dataset, dir, NotEnoughTransactions numberOfTxs totalTxs) + | numberOfTxs /= totalTxs -> pure $ Left (dataset, dir, summary, NotEnoughTransactions numberOfTxs totalTxs) | numberOfInvalidTxs == 0 -> pure $ Right summary - | otherwise -> pure $ Left (dataset, dir, InvalidTransactions numberOfInvalidTxs) + | otherwise -> pure $ Left (dataset, dir, summary, InvalidTransactions numberOfInvalidTxs) run outputDirectory datasetFiles action = do results <- forM datasetFiles $ \datasetPath -> do @@ -89,11 +89,19 @@ main = do runSingle dataset action dir summarizeResults outputDirectory results + summarizeResults :: Maybe FilePath -> [Either (Dataset, FilePath, Summary, BenchmarkFailed) Summary] -> IO () summarizeResults outputDirectory results = do let (failures, summaries) = partitionEithers results case failures of - [] -> benchmarkSucceeded outputDirectory summaries - errs -> mapM_ (\(_, dir, exc) -> benchmarkFailedWith dir exc) errs >> exitFailure + [] -> writeBenchmarkReport Nothing outputDirectory summaries + errs -> + mapM_ + ( \((_, dir, summary, exc), errorNbr :: Int) -> + writeBenchmarkReport (Just $ "failure-" <> show errorNbr) outputDirectory [summary] + >> benchmarkFailedWith dir exc + ) + (errs `zip` [1 ..]) + >> exitFailure loadDataset :: FilePath -> IO Dataset loadDataset f = do @@ -129,15 +137,15 @@ benchmarkFailedWith benchDir = \case where formatLocation = maybe "" (\loc -> "at " <> prettySrcLoc loc) -benchmarkSucceeded :: Maybe FilePath -> [Summary] -> IO () -benchmarkSucceeded outputDirectory summaries = do +writeBenchmarkReport :: Maybe String -> Maybe FilePath -> [Summary] -> IO () +writeBenchmarkReport lbl outputDirectory summaries = do dumpToStdout whenJust outputDirectory writeReport where dumpToStdout = mapM_ putTextLn (concatMap textReport summaries) writeReport outputDir = do - let reportPath = outputDir "end-to-end-benchmarks.md" + let reportPath = outputDir (maybe "" (<> "-") lbl <> "end-to-end-benchmarks.md") putStrLn $ "Writing report to: " <> reportPath now <- getCurrentTime let report = markdownReport now summaries From 0ccb72d72982b6ca079b68a7bc10764294ddc807 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 28 Aug 2024 11:01:43 +0100 Subject: [PATCH 097/115] Make peers part of the matrix --- .github/workflows/network-test.yaml | 34 ++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index f95cf7c926c..4bbc0c4dee2 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -29,23 +29,31 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + # Note: At present we can only run for 3 peers; to configure this for + # more we need to make the docker-compose spin-up dynamic across + # however many we would like to configure here. + peers: [3] scaling_factor: [10] netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] expect_failure: [false] # We know these will fail; so don't try. exclude: - - scaling_factor: 10 + - peers: 3 + scaling_factor: 10 netem_loss: 10 expect_failure: false - - scaling_factor: 10 + - peers: 3 + scaling_factor: 10 netem_loss: 20 expect_failure: false # In fact, allow them to fail. include: - - scaling_factor: 10 + - peers: 3 + scaling_factor: 10 netem_loss: 10 expect_failure: true - - scaling_factor: 10 + - peers: 3 + scaling_factor: 10 netem_loss: 20 expect_failure: true steps: @@ -105,6 +113,7 @@ jobs: continue-on-error: ${{ matrix.expect_failure }} run: | # Extract inputs with defaults for non-workflow_dispatch events + peers="${{ matrix.peers }}" percent="${{ matrix.netem_loss }}" scaling_factor="${{ matrix.scaling_factor }}" target_peer="${{ github.event.inputs.target_peer || 'alice' }}" @@ -119,9 +128,16 @@ jobs: .github/workflows/network/watch_logs.sh $target_peer $peers_info_json fi + client_args="" + for i in $(seq 1 $peers); do + port=$((4000 + $i)) + client_args+=" --hydra-client=localhost:$port" + done + # Run benchmark on demo mkdir benchmarks touch benchmarks/test.log + nix run .#legacyPackages.x86_64-linux.hydra-cluster.components.benchmarks.bench-e2e -- \ demo \ --output-directory=benchmarks \ @@ -129,9 +145,7 @@ jobs: --timeout=1000s \ --testnet-magic 42 \ --node-socket=demo/devnet/node.socket \ - --hydra-client=localhost:4001 \ - --hydra-client=localhost:4002 \ - --hydra-client=localhost:4003 + $client_args - name: Acquire logs if: always() @@ -143,7 +157,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: "docker-logs-netem-loss=${{ matrix.netem_loss }},scaling_factor=${{ matrix.scaling_factor }}" + name: "docker-logs-netem-loss=${{ matrix.netem_loss }},scaling_factor=${{ matrix.scaling_factor }},peers=${{ matrix.peers }}" path: demo/docker-logs if-no-files-found: ignore @@ -151,6 +165,6 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: "benchmarks-netem-loss=${{ matrix.netem_loss }},scaling_factor=${{ matrix.scaling_factor }}" + name: "benchmarks-netem-loss=${{ matrix.netem_loss }},scaling_factor=${{ matrix.scaling_factor }},peers=${{ matrix.peers }}" path: benchmarks - if-no-files-found: ignore \ No newline at end of file + if-no-files-found: ignore From dd2051fef2374cd3c4ad8e63acce5f00f76722d6 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 28 Aug 2024 11:17:13 +0100 Subject: [PATCH 098/115] Allow 5% to fail --- .github/workflows/network-test.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 4bbc0c4dee2..4d8669cfc55 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -46,6 +46,10 @@ jobs: scaling_factor: 10 netem_loss: 20 expect_failure: false + - peers: 3 + scaling_factor: 10 + netem_loss: 5 + expect_failure: false # In fact, allow them to fail. include: - peers: 3 @@ -56,6 +60,10 @@ jobs: scaling_factor: 10 netem_loss: 20 expect_failure: true + - peers: 3 + scaling_factor: 10 + netem_loss: 5 + expect_failure: true steps: - uses: actions/checkout@v4 with: From b4c3376a54da337a59a3cfeab31d50bb2e461836 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 28 Aug 2024 11:23:55 +0100 Subject: [PATCH 099/115] Remove explicit failure tracking in the includes/excludes --- .github/workflows/network-test.yaml | 38 ++++++----------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 4d8669cfc55..b589055a780 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -35,35 +35,6 @@ jobs: peers: [3] scaling_factor: [10] netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] - expect_failure: [false] - # We know these will fail; so don't try. - exclude: - - peers: 3 - scaling_factor: 10 - netem_loss: 10 - expect_failure: false - - peers: 3 - scaling_factor: 10 - netem_loss: 20 - expect_failure: false - - peers: 3 - scaling_factor: 10 - netem_loss: 5 - expect_failure: false - # In fact, allow them to fail. - include: - - peers: 3 - scaling_factor: 10 - netem_loss: 10 - expect_failure: true - - peers: 3 - scaling_factor: 10 - netem_loss: 20 - expect_failure: true - - peers: 3 - scaling_factor: 10 - netem_loss: 5 - expect_failure: true steps: - uses: actions/checkout@v4 with: @@ -118,7 +89,14 @@ jobs: limit-access-to-actor: true - name: Run pumba and the benchmarks - continue-on-error: ${{ matrix.expect_failure }} + # Note: We're going to allow everything to fail. In the job on GitHub, + # we will be able to see which ones _did_, in fact, fail. Originally, + # we were keeping track of our expectations with 'include' and + # 'exclude' directives here, but I think it's best to leave those out, + # as some of the tests (say 5%) fail, and overall the conditions of + # failure depend on the scaling factor, the peers, etc, and it becomes + # too complicated to track here. + continue-on-error: true run: | # Extract inputs with defaults for non-workflow_dispatch events peers="${{ matrix.peers }}" From d7b18ab11eb3de60f6cdcfd45307560c41c63e1e Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 28 Aug 2024 11:24:32 +0100 Subject: [PATCH 100/115] Add extra scaling factor --- .github/workflows/network-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index b589055a780..a0146694bb4 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -33,7 +33,7 @@ jobs: # more we need to make the docker-compose spin-up dynamic across # however many we would like to configure here. peers: [3] - scaling_factor: [10] + scaling_factor: [10, 50] netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] steps: - uses: actions/checkout@v4 From fc2622707c43e875a59b189666c73555b5edcb50 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Wed, 28 Aug 2024 11:36:27 +0100 Subject: [PATCH 101/115] Matrix information in the name --- .github/workflows/network-test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index a0146694bb4..90db31cce9d 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -35,6 +35,7 @@ jobs: peers: [3] scaling_factor: [10, 50] netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] + name: "Peers: ${{ matrix.peers }}, scaling: ${{ matrix.scaling_factor }}, loss: ${{ matrix.netem_loss }}" steps: - uses: actions/checkout@v4 with: From 4f31cfe359358b89f3d1a16ea011dd7d835a95d8 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 13:11:07 +0200 Subject: [PATCH 102/115] Rename hydra clients peers to api hosts --- hydra-cluster/bench/Bench/EndToEnd.hs | 8 ++++---- hydra-cluster/src/HydraNode.hs | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index ab91956fc28..347ced533a4 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -137,11 +137,11 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas (leader : followers) -> scenario hydraTracer node workDir dataset mempty leader followers where - withHydraClientConnections tracer peers connections action = do - case peers of + withHydraClientConnections tracer apiHosts connections action = do + case apiHosts of [] -> action connections - ((peer, peerId) : rest) -> do - withConnectionToNodeHost tracer peerId peer False $ \con -> do + ((apiHost, peerId) : rest) -> do + withConnectionToNodeHost tracer peerId apiHost False $ \con -> do withHydraClientConnections tracer rest (con : connections) action returnFaucetFunds tracer node cKeys = do diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index 1bbb15bf277..b7b1ee7ca10 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -48,7 +48,7 @@ import Prelude qualified data HydraClient = HydraClient { hydraNodeId :: Int - , peer :: Host + , apiHost :: Host , connection :: Connection , tracer :: Tracer IO HydraNodeLog } @@ -173,7 +173,7 @@ waitForAll tracer delay nodes expected = do -- | Helper to make it easy to obtain a commit tx using some wallet utxo. -- Create a commit tx using the hydra-node for later submission. requestCommitTx :: HydraClient -> UTxO -> IO Tx -requestCommitTx HydraClient{peer = Host{hostname, port}} utxos = +requestCommitTx HydraClient{apiHost = Host{hostname, port}} utxos = runReq defaultHttpConfig request <&> commitTx . responseBody where request = @@ -186,7 +186,7 @@ requestCommitTx HydraClient{peer = Host{hostname, port}} utxos = -- | Submit a decommit transaction to the hydra-node. postDecommit :: HydraClient -> Tx -> IO () -postDecommit HydraClient{peer = Host{hostname, port}} decommitTx = do +postDecommit HydraClient{apiHost = Host{hostname, port}} decommitTx = do void $ parseUrlThrow ("POST http://" <> T.unpack hostname <> ":" <> show port <> "/decommit") <&> setRequestBodyJSON decommitTx @@ -196,7 +196,7 @@ postDecommit HydraClient{peer = Host{hostname, port}} decommitTx = do -- avoid parsing responses using the same data types as the system under test, -- this parses the response as a 'UTxO' type as we often need to pick it apart. getSnapshotUTxO :: HydraClient -> IO UTxO -getSnapshotUTxO HydraClient{peer = Host{hostname, port}} = +getSnapshotUTxO HydraClient{apiHost = Host{hostname, port}} = runReq defaultHttpConfig request <&> responseBody where request = @@ -208,7 +208,7 @@ getSnapshotUTxO HydraClient{peer = Host{hostname, port}} = (Req.port (fromInteger . toInteger $ port)) getMetrics :: HasCallStack => HydraClient -> IO ByteString -getMetrics HydraClient{hydraNodeId, peer = Host{hostname}} = do +getMetrics HydraClient{hydraNodeId, apiHost = Host{hostname}} = do failAfter 3 $ try (runReq defaultHttpConfig request) >>= \case Left (e :: HttpException) -> failure $ "Request for hydra-node metrics failed: " <> show e @@ -410,7 +410,7 @@ withConnectionToNode tracer hydraNodeId = port = fromInteger $ 4_000 + toInteger hydraNodeId withConnectionToNodeHost :: forall a. Tracer IO HydraNodeLog -> Int -> Host -> Bool -> (HydraClient -> IO a) -> IO a -withConnectionToNodeHost tracer hydraNodeId peer@Host{hostname, port} showHistory action = do +withConnectionToNodeHost tracer hydraNodeId apiHost@Host{hostname, port} showHistory action = do connectedOnce <- newIORef False tryConnect connectedOnce (200 :: Int) where @@ -433,7 +433,7 @@ withConnectionToNodeHost tracer hydraNodeId peer@Host{hostname, port} showHistor doConnect connectedOnce = runClient (T.unpack hostname) (fromInteger . toInteger $ port) historyMode $ \connection -> do atomicWriteIORef connectedOnce True traceWith tracer (NodeStarted hydraNodeId) - res <- action $ HydraClient{hydraNodeId, peer, connection, tracer} + res <- action $ HydraClient{hydraNodeId, apiHost, connection, tracer} sendClose connection ("Bye" :: Text) pure res From cc59644fb34351d20a4902eea608550770859f27 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 13:18:27 +0200 Subject: [PATCH 103/115] Update README --- hydra-cluster/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hydra-cluster/README.md b/hydra-cluster/README.md index f393a40f5e3..a64fb02e032 100644 --- a/hydra-cluster/README.md +++ b/hydra-cluster/README.md @@ -147,7 +147,7 @@ The benchmark can be also run over the running `demo` hydra-cluster, using `caba `results.csv` file in a work directory. Same as for benchmarks results, you can use the `bench/plot.sh` script to plot the transaction confirmation times. To run the benchmark in this mode, the command is: -* `demo`: Runs one or more preexisting _datasets_ in sequence and collect their results in a single markdown formatted file. The purpose of this setup is to facilitate a variaty of network-resiliance scenarios, such as packet loss or node failures. This is useful to prove the robustness and performance of the hydra-node's network over time and produce a human-readable summary. +* `demo`: Runs a single _dataset_ freshly generated and collects its results in a markdown formatted file. The purpose of this setup is to facilitate a variaty of network-resiliance scenarios, such as packet loss or node failures. This is useful to prove the robustness and performance of the hydra-node's network over time and produce a human-readable summary. For instance, we make use of this in our [CI](https://github.com/cardano-scaling/hydra/blob/master/.github/workflows/network-test.yaml) to keep track for scenarios that we care about. From 116976fbd60047ac426b8286ce4e61df2d68c788 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 13:23:02 +0200 Subject: [PATCH 104/115] Enhance user error during processTransactions --- hydra-cluster/bench/Bench/EndToEnd.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 347ced533a4..204ab3d01f0 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -362,8 +362,8 @@ processTransactions clients clientDatasets = do `concurrently_` waitForAllConfirmations client registry (Set.fromList $ map txId txSequence) `concurrently_` progressReport (hydraNodeId client) clientId numberOfTxs submissionQ ) - `catch` \(_ :: SomeException) -> - putStrLn "Time exceeded while wait for all confirmations" + `catch` \(ex :: SomeException) -> + putStrLn ("Something went wrong while waiting for all confirmations: " <> show ex) readTVarIO (processedTxs registry) progressReport :: Int -> Int -> Int -> TBQueue IO Tx -> IO () From d1d96fd26cab01f2126a42073ba72f5d55ebba2c Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 13:26:05 +0200 Subject: [PATCH 105/115] Update bench demo option description --- hydra-cluster/bench/Bench/Options.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hydra-cluster/bench/Bench/Options.hs b/hydra-cluster/bench/Bench/Options.hs index d883d77d6a0..ee5eb7011ee 100644 --- a/hydra-cluster/bench/Bench/Options.hs +++ b/hydra-cluster/bench/Bench/Options.hs @@ -157,7 +157,7 @@ demoOptionsInfo = "Run bench scenario over local demo. \ \ This requires having in the background: \ \ * cardano node running on specified node-socket. \ - \ * three hydra nodes listening on ports 4001, 4002 and 4003." + \ * hydra nodes listening on specified hosts." ) demoOptionsParser :: Parser Options From a5185a58b1f6324ae62085bbc52568e808e50f9d Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 16:22:49 +0200 Subject: [PATCH 106/115] Make the scenario to seed the network --- hydra-cluster/bench/Bench/EndToEnd.hs | 36 +++++++++++---------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 204ab3d01f0..fd12426afa2 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -45,7 +45,6 @@ import Hydra.Network (Host) import Hydra.Party (Party, deriveParty) import HydraNode ( HydraClient, - HydraNodeLog, hydraNodeId, input, output, @@ -92,15 +91,15 @@ bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets} = do let parties = Set.fromList (deriveParty <$> hydraKeys) withOSStats workDir $ withCardanoNodeDevnet (contramap FromCardanoNode tracer) workDir $ \node@RunningNode{nodeSocket} -> do - putTextLn "Seeding network" + putTextLn "Publishing hydra scripts" + hydraScriptsTxId <- publishHydraScriptsAs node Faucet + putStrLn $ "Starting hydra cluster in " <> workDir let hydraTracer = contramap FromHydraNode tracer - hydraScriptsTxId <- seedNetwork node dataset (contramap FromFaucet tracer) let contestationPeriod = UnsafeContestationPeriod 10 - putStrLn $ "Starting hydra cluster in " <> workDir withHydraCluster hydraTracer workDir nodeSocket startingNodeId cardanoKeys hydraKeys hydraScriptsTxId contestationPeriod $ \(leader :| followers) -> do let clients = leader : followers waitForNodesConnected hydraTracer 20 clients - scenario hydraTracer node workDir dataset parties leader followers + scenario tracer node workDir dataset parties leader followers benchDemo :: NetworkId -> @@ -110,7 +109,7 @@ benchDemo :: FilePath -> Dataset -> IO Summary -benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Dataset{clientDatasets, fundingTransaction} = do +benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Dataset{clientDatasets} = do putStrLn $ "Test logs available in: " <> (workDir "test.log") withFile (workDir "test.log") ReadWriteMode $ \hdl -> withTracerOutputTo hdl "Test" $ \tracer -> @@ -123,19 +122,12 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas Just node -> do let clientSks = clientKeys <$> clientDatasets (`finally` returnFaucetFunds tracer node clientSks) $ do - putTextLn "Seeding network" - submitTransaction networkId nodeSocket fundingTransaction - void $ awaitTransaction networkId nodeSocket fundingTransaction - forM_ clientSks $ \ClientKeys{signingKey} -> do - let vk = getVerificationKey signingKey - putTextLn $ "Seed client " <> show vk - seedFromFaucet node vk 100_000_000 (contramap FromFaucet tracer) putStrLn $ "Connecting to hydra cluster: " <> show hydraClients let hydraTracer = contramap FromHydraNode tracer withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case [] -> error "no hydra clients provided" (leader : followers) -> - scenario hydraTracer node workDir dataset mempty leader followers + scenario tracer node workDir dataset mempty leader followers where withHydraClientConnections tracer apiHosts connections action = do case apiHosts of @@ -156,7 +148,7 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas senders scenario :: - Tracer IO HydraNodeLog -> + Tracer IO EndToEndLog -> RunningNode -> FilePath -> Dataset -> @@ -164,7 +156,12 @@ scenario :: HydraClient -> [HydraClient] -> IO Summary -scenario hydraTracer node workDir Dataset{clientDatasets, title, description} parties leader followers = do +scenario tracer node workDir dataset@Dataset{clientDatasets, title, description} parties leader followers = do + let hydraTracer = contramap FromHydraNode tracer + + putTextLn "Seeding network" + seedNetwork node dataset (contramap FromFaucet tracer) + let clusterSize = fromIntegral $ length clientDatasets let clients = leader : followers let totalTxs = sum $ map (length . txSequence) clientDatasets @@ -317,14 +314,11 @@ movingAverage confirmations = in map average fiveSeconds -- | Distribute 100 ADA fuel, starting funds from faucet for each client in the --- dataset, and also publish the hydra scripts. The 'TxId' of the publishing --- transaction is returned. -seedNetwork :: RunningNode -> Dataset -> Tracer IO FaucetLog -> IO TxId +-- dataset. +seedNetwork :: RunningNode -> Dataset -> Tracer IO FaucetLog -> IO () seedNetwork node@RunningNode{nodeSocket, networkId} Dataset{fundingTransaction, clientDatasets} tracer = do fundClients forM_ (clientKeys <$> clientDatasets) fuelWith100Ada - putTextLn "Publishing hydra scripts" - publishHydraScriptsAs node Faucet where fundClients = do putTextLn "Fund scenario from faucet" From 02d460d11de4b1e62c98987d70a7656cdc090a6c Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 16:28:36 +0200 Subject: [PATCH 107/115] Rever changes over the benchmarks slug used by the website --- hydra-cluster/bench/Main.hs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hydra-cluster/bench/Main.hs b/hydra-cluster/bench/Main.hs index 97fd30be957..f0930c9ad9a 100644 --- a/hydra-cluster/bench/Main.hs +++ b/hydra-cluster/bench/Main.hs @@ -93,14 +93,14 @@ main = do summarizeResults outputDirectory results = do let (failures, summaries) = partitionEithers results case failures of - [] -> writeBenchmarkReport Nothing outputDirectory summaries + [] -> writeBenchmarkReport outputDirectory summaries errs -> mapM_ - ( \((_, dir, summary, exc), errorNbr :: Int) -> - writeBenchmarkReport (Just $ "failure-" <> show errorNbr) outputDirectory [summary] + ( \(_, dir, summary, exc) -> + writeBenchmarkReport outputDirectory [summary] >> benchmarkFailedWith dir exc ) - (errs `zip` [1 ..]) + errs >> exitFailure loadDataset :: FilePath -> IO Dataset @@ -137,15 +137,15 @@ benchmarkFailedWith benchDir = \case where formatLocation = maybe "" (\loc -> "at " <> prettySrcLoc loc) -writeBenchmarkReport :: Maybe String -> Maybe FilePath -> [Summary] -> IO () -writeBenchmarkReport lbl outputDirectory summaries = do +writeBenchmarkReport :: Maybe FilePath -> [Summary] -> IO () +writeBenchmarkReport outputDirectory summaries = do dumpToStdout whenJust outputDirectory writeReport where dumpToStdout = mapM_ putTextLn (concatMap textReport summaries) writeReport outputDir = do - let reportPath = outputDir (maybe "" (<> "-") lbl <> "end-to-end-benchmarks.md") + let reportPath = outputDir "end-to-end-benchmarks.md" putStrLn $ "Writing report to: " <> reportPath now <- getCurrentTime let report = markdownReport now summaries From 36ac132c32e5174d7928d6cbe6629549d6c57c3f Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 16:39:04 +0200 Subject: [PATCH 108/115] Do not trace signing keys during benchmark --- hydra-cluster/src/Hydra/Cluster/Faucet.hs | 26 ++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/hydra-cluster/src/Hydra/Cluster/Faucet.hs b/hydra-cluster/src/Hydra/Cluster/Faucet.hs index 59c20e4a96c..a2322ea3e83 100644 --- a/hydra-cluster/src/Hydra/Cluster/Faucet.hs +++ b/hydra-cluster/src/Hydra/Cluster/Faucet.hs @@ -25,7 +25,7 @@ import Hydra.Chain.CardanoClient (queryProtocolParameters) import Hydra.Chain.Direct.ScriptRegistry ( publishHydraScripts, ) -import Hydra.Cluster.Fixture (Actor (Faucet), actorName) +import Hydra.Cluster.Fixture (Actor (Faucet)) import Hydra.Cluster.Util (keysFor) import Hydra.Ledger (balance) import Hydra.Ledger.Cardano () @@ -108,8 +108,7 @@ returnFundsToFaucet :: IO () returnFundsToFaucet tracer node sender = do senderKeys <- keysFor sender - returnAmount <- returnFundsToFaucet' tracer node (snd senderKeys) - traceWith tracer $ ReturnedFunds{actor = actorName sender, returnAmount} + void $ returnFundsToFaucet' tracer node (snd senderKeys) returnFundsToFaucet' :: Tracer IO FaucetLog -> @@ -121,15 +120,18 @@ returnFundsToFaucet' tracer RunningNode{networkId, nodeSocket} senderSk = do let faucetAddress = mkVkAddress networkId faucetVk let senderVk = getVerificationKey senderSk utxo <- queryUTxOFor networkId nodeSocket QueryTip senderVk - if null utxo - then pure 0 - else retryOnExceptions tracer $ do - let utxoValue = balance @Tx utxo - let allLovelace = selectLovelace utxoValue - tx <- sign senderSk <$> buildTxBody utxo faucetAddress - submitTransaction networkId nodeSocket tx - void $ awaitTransaction networkId nodeSocket tx - pure allLovelace + returnAmount <- + if null utxo + then pure 0 + else retryOnExceptions tracer $ do + let utxoValue = balance @Tx utxo + let allLovelace = selectLovelace utxoValue + tx <- sign senderSk <$> buildTxBody utxo faucetAddress + submitTransaction networkId nodeSocket tx + void $ awaitTransaction networkId nodeSocket tx + pure allLovelace + traceWith tracer $ ReturnedFunds{actor = show senderVk, returnAmount} + pure returnAmount where buildTxBody utxo faucetAddress = -- Here we specify no outputs in the transaction so that a change output with the From cdf5ff651962e56a715e81d998047726ca352dd7 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 16:46:44 +0200 Subject: [PATCH 109/115] Remove watch logs step and simplify workflow args --- .github/workflows/network-test.yaml | 34 ++--------- .github/workflows/network/watch_logs.sh | 79 ------------------------- 2 files changed, 5 insertions(+), 108 deletions(-) delete mode 100755 .github/workflows/network/watch_logs.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 90db31cce9d..567c2dd7082 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -9,20 +9,6 @@ on: description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false - wait_peer_disconnected: - type: boolean - description: 'Check other peers see the target peer is disconnected before running benchmark.' - required: false - default: false - target_peer: - description: 'Select the peer to target with netem package loss' - required: true - type: choice - options: - - alice - - bob - - carol - default: alice jobs: network-test: @@ -32,6 +18,7 @@ jobs: # Note: At present we can only run for 3 peers; to configure this for # more we need to make the docker-compose spin-up dynamic across # however many we would like to configure here. + # Currently this is just a label and does not have any functional impact. peers: [3] scaling_factor: [10, 50] netem_loss: [0, 1, 2, 3, 4, 5, 10, 20] @@ -100,27 +87,14 @@ jobs: continue-on-error: true run: | # Extract inputs with defaults for non-workflow_dispatch events - peers="${{ matrix.peers }}" percent="${{ matrix.netem_loss }}" scaling_factor="${{ matrix.scaling_factor }}" - target_peer="${{ github.event.inputs.target_peer || 'alice' }}" + target_peer="alice" peers_info_json=$(.github/workflows/network/peers_info_json.sh) .github/workflows/network/run_pumba.sh $target_peer $percent $peers_info_json - wait_peer_disconnected="${{ github.event.inputs.wait_peer_disconnected || false }}" - - if [[ "$wait_peer_disconnected" == "true" ]]; then - .github/workflows/network/watch_logs.sh $target_peer $peers_info_json - fi - - client_args="" - for i in $(seq 1 $peers); do - port=$((4000 + $i)) - client_args+=" --hydra-client=localhost:$port" - done - # Run benchmark on demo mkdir benchmarks touch benchmarks/test.log @@ -132,7 +106,9 @@ jobs: --timeout=1000s \ --testnet-magic 42 \ --node-socket=demo/devnet/node.socket \ - $client_args + --hydra-client=localhost:4001 \ + --hydra-client=localhost:4002 \ + --hydra-client=localhost:4003 - name: Acquire logs if: always() diff --git a/.github/workflows/network/watch_logs.sh b/.github/workflows/network/watch_logs.sh deleted file mode 100755 index fac7fdfc185..00000000000 --- a/.github/workflows/network/watch_logs.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash - -if ! command -v jq &> /dev/null -then - echo "jq is required for this script ~ install it." - exit 1 -fi - -target_peer=$1 - -peers_info_json=$2 - -peers_to_watch_json=$(echo "$peers_info_json" | jq -r --arg selected_peer "$target_peer" ' - to_entries - | map(select(.key != $selected_peer)) - | map(.key) -') - -logs_file_json=$(echo "$peers_info_json" | jq --arg selected_peer "$target_peer" ' - to_entries - | map(select(.key != $selected_peer)) - | map({ - (.key): (.value.info.persistence_dir | sub("^/"; "")) - }) - | add -') - -target_peer_id=$(echo "$peers_info_json" \ - | jq -r --arg selected_peer "$target_peer" '.[$selected_peer].info.node_id') - -check_log() { - local peer=$1 - local peer_log_file=$(echo "$logs_file_json" | jq -r --arg peer "$peer" '.[$peer]') - if jq --arg target_peer_id $target_peer_id \ - 'select(.tag == "PeerDisconnected" and .peer == $target_peer_id)' \ - "demo/$peer_log_file/server-output" | grep -q .; then - echo "Success for peer: $peer" - return 0 # Success: log file has output - else - echo "Failure for peer: $peer" - return 1 # Failure: log file is empty or doesn't exist - fi -} - -declare -A peer_ready -for peer in $(echo "$peers_to_watch_json" | jq -r '.[]'); do - peer_ready[$peer]=false -done - -while true; do - # Assume all are ready until proven otherwise - all_ready=true - - for peer in "${!peer_ready[@]}"; do - echo "Checking $peer..." - - if [[ "${peer_ready[$peer]}" == false ]]; then - check_log "$peer" - # Check the exit status of the check_log function - if [[ $? -eq 0 ]]; then - echo "$peer is now marked as ready." - peer_ready[$peer]=true - fi - fi - - # Check if the current peer is not ready - if [[ "${peer_ready[$peer]}" == false ]]; then - # If any peer is not ready, set all_ready to false - all_ready=false - fi - done - - if [[ "$all_ready" == true ]]; then - echo "All peers are ready!" - break - fi - - sleep 5 -done \ No newline at end of file From 5e69dd728519a59ab24013700d000c885bb7c49b Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 17:43:12 +0200 Subject: [PATCH 110/115] Remove unnecessary scripts --- .github/workflows/network-test.yaml | 7 +-- .github/workflows/network/peers_info_json.sh | 60 -------------------- .github/workflows/network/run_pumba.sh | 18 +----- 3 files changed, 6 insertions(+), 79 deletions(-) delete mode 100755 .github/workflows/network/peers_info_json.sh diff --git a/.github/workflows/network-test.yaml b/.github/workflows/network-test.yaml index 567c2dd7082..5c45204b501 100644 --- a/.github/workflows/network-test.yaml +++ b/.github/workflows/network-test.yaml @@ -89,11 +89,10 @@ jobs: # Extract inputs with defaults for non-workflow_dispatch events percent="${{ matrix.netem_loss }}" scaling_factor="${{ matrix.scaling_factor }}" - target_peer="alice" + target_peer="hydra-node-1" + other_peers="172.16.238.20 172.16.238.30" - peers_info_json=$(.github/workflows/network/peers_info_json.sh) - - .github/workflows/network/run_pumba.sh $target_peer $percent $peers_info_json + .github/workflows/network/run_pumba.sh $target_peer $percent $other_peers # Run benchmark on demo mkdir benchmarks diff --git a/.github/workflows/network/peers_info_json.sh b/.github/workflows/network/peers_info_json.sh deleted file mode 100755 index 8e1936242eb..00000000000 --- a/.github/workflows/network/peers_info_json.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash - -if ! command -v jq &> /dev/null -then - echo "jq is required for this script ~ install it." - exit 1 -fi - -extract_hydra_node_info_json() { - local node_name=$1 - - local container_name="demo-$node_name-1" - - local network=$(docker inspect "$container_name" | jq '.[0].NetworkSettings.Networks.demo_hydra_net.IPAMConfig.IPv4Address' | tr -d '"') - - local command_list=$(docker inspect "$container_name" | jq '.[0].Config.Cmd') - - local json=$( - echo "$command_list" | jq -r ' - . as $arr | - reduce range(0; ($arr | length) / 2) as $i ( - {}; - . + {($arr[2 * $i]): $arr[2 * $i + 1]} - ) - | with_entries( - select( - .key == "--node-id" or - .key == "--api-host" or - .key == "--host" or - .key == "--hydra-signing-key" or - .key == "--cardano-signing-key" or - .key == "--ledger-protocol-parameters" or - .key == "--testnet-magic" or - .key == "--persistence-dir" - ) - ) - | with_entries( - .key |= sub("^--"; "") | # Remove -- - .key |= gsub("-"; "_") # Replace - with _ - )' - ) - echo "{\"node_name\": \"$node_name\", \"info\": $json, \"network\": \"$network\" }" -} - -peers_info_json() { - echo "{ - \"alice\": $(extract_hydra_node_info_json "hydra-node-1"), - \"bob\": $(extract_hydra_node_info_json "hydra-node-2"), - \"carol\": $(extract_hydra_node_info_json "hydra-node-3") - }" -} - -json_result=$(echo $(peers_info_json) | jq -c) - -if [[ -z "$json_result" ]]; then - echo "Error: json_result is empty." - exit 1 -fi - -echo $json_result diff --git a/.github/workflows/network/run_pumba.sh b/.github/workflows/network/run_pumba.sh index 94fc2f85504..d881f5e2ecf 100755 --- a/.github/workflows/network/run_pumba.sh +++ b/.github/workflows/network/run_pumba.sh @@ -1,29 +1,17 @@ #!/usr/bin/env bash -if ! command -v jq &> /dev/null -then - echo "jq is required for this script ~ install it." - exit 1 -fi - -target_peer=$1 +target_node_name=$1 percent=$2 -peers_info_json=$3 - -target_node_name=$(echo "$peers_info_json" | jq -r ".$target_peer.node_name") +rest_node_names=$3 # Build Pumba netem command -unselected_networks=$(echo "$peers_info_json" | jq -r --arg selected_peer "$target_peer" ' - to_entries[] | select(.key != $selected_peer) | .value.network -') - nix_command="nix run github:noonio/pumba/noon/add-flake -- -l debug --random netem --duration 20m" while IFS= read -r network; do nix_command+=" --target $network" -done <<< "$unselected_networks" +done <<< "$rest_node_names" nix_command+=" loss --percent \"$percent\" \"re2:$target_node_name\" &" From a555aec61a681d30d33a1d8173d70ee6725350c8 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 28 Aug 2024 17:51:36 +0200 Subject: [PATCH 111/115] Enhance failure message for HUnitFailure errors --- hydra-cluster/bench/Bench/EndToEnd.hs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index fd12426afa2..c65fd755fb5 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -66,6 +66,7 @@ import System.Process ( proc, withCreateProcess, ) +import Test.HUnit.Lang (formatFailureReason) import Text.Printf (printf) import Text.Regex.TDFA (getAllTextMatches, (=~)) import Prelude (read) @@ -347,6 +348,8 @@ processTransactions clients clientDatasets = do let processors = zip (zip clientDatasets (cycle clients)) [1 ..] mconcat <$> mapConcurrently (uncurry clientProcessDataset) processors where + formatLocation = maybe "" (\loc -> "at " <> prettySrcLoc loc) + clientProcessDataset (ClientDataset{txSequence}, client) clientId = do let numberOfTxs = length txSequence submissionQ <- newTBQueueIO (fromIntegral numberOfTxs) @@ -356,8 +359,10 @@ processTransactions clients clientDatasets = do `concurrently_` waitForAllConfirmations client registry (Set.fromList $ map txId txSequence) `concurrently_` progressReport (hydraNodeId client) clientId numberOfTxs submissionQ ) - `catch` \(ex :: SomeException) -> - putStrLn ("Something went wrong while waiting for all confirmations: " <> show ex) + `catch` \(HUnitFailure sourceLocation reason) -> + putStrLn ("Something went wrong while waiting for all confirmations: " <> formatLocation sourceLocation <> ": " <> formatFailureReason reason) + `catch` \(ex :: SomeException) -> + putStrLn ("Something went wrong while waiting for all confirmations: " <> show ex) readTVarIO (processedTxs registry) progressReport :: Int -> Int -> Int -> TBQueue IO Tx -> IO () From 3aafd9cd4a093624b6d72cb5308d5b09896bc259 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 29 Aug 2024 11:45:38 +0200 Subject: [PATCH 112/115] Remove hack to check every node sees head is initialized for demo In this case the parties are unknown but we still need to fetch the headId from a head id observation. --- hydra-cluster/bench/Bench/EndToEnd.hs | 13 ++++++++----- hydra-cluster/src/Hydra/Cluster/Scenarios.hs | 8 -------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index c65fd755fb5..27b62f5d285 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -22,6 +22,7 @@ import Control.Lens (to, (^?)) import Control.Monad.Class.MonadAsync (mapConcurrently) import Data.Aeson (Result (Error, Success), Value, encode, fromJSON, (.=)) import Data.Aeson.Lens (key, _Array, _JSON, _Number, _String) +import Data.Aeson.Types (parseMaybe) import Data.List qualified as List import Data.Map qualified as Map import Data.Scientific (Scientific) @@ -33,12 +34,12 @@ import Hydra.Cluster.Faucet (FaucetLog (..), publishHydraScriptsAs, returnFundsT import Hydra.Cluster.Fixture (Actor (..)) import Hydra.Cluster.Scenarios ( EndToEndLog (..), - aHeadIsInitializingWith, headIsInitializingWith, ) import Hydra.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) import Hydra.Crypto (generateSigningKey) import Hydra.Generator (ClientDataset (..), ClientKeys (..), Dataset (..)) +import Hydra.HeadId (HeadId) import Hydra.Ledger (txId) import Hydra.Logging (Tracer, traceWith, withTracerOutputTo) import Hydra.Network (Host) @@ -170,10 +171,12 @@ scenario tracer node workDir dataset@Dataset{clientDatasets, title, description} putTextLn "Initializing Head" send leader $ input "Init" [] headId <- - waitForAllMatch (fromIntegral $ 10 * clusterSize) clients $ - if null parties - then aHeadIsInitializingWith (length clients) - else headIsInitializingWith parties + waitForAllMatch (fromIntegral $ 10 * clusterSize) clients $ \v -> + headIsInitializingWith parties v + <|> do + guard $ v ^? key "tag" == Just "HeadIsInitializing" + headId <- v ^? key "headId" + parseMaybe parseJSON headId :: Maybe HeadId putTextLn "Comitting initialUTxO from dataset" expectedUTxO <- commitUTxO node clients clientDatasets diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index cdf9ce28096..3b31782b2ca 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -758,14 +758,6 @@ headIsInitializingWith expectedParties v = do headId <- v ^? key "headId" parseMaybe parseJSON headId -aHeadIsInitializingWith :: Int -> Value -> Maybe HeadId -aHeadIsInitializingWith nbrParties v = do - guard $ v ^? key "tag" == Just "HeadIsInitializing" - parties :: Set Party <- v ^? key "parties" >>= parseMaybe parseJSON - guard $ length parties == nbrParties - headId <- v ^? key "headId" - parseMaybe parseJSON headId - expectErrorStatus :: -- | Expected http status code Int -> From 710d65fd7fded912ee541364ed242e80364bd743 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 29 Aug 2024 11:53:49 +0200 Subject: [PATCH 113/115] Refactor hacky boolean blindness to manage query params on withConnectionToNode --- hydra-cluster/bench/Bench/EndToEnd.hs | 2 +- hydra-cluster/src/HydraNode.hs | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index 27b62f5d285..da8d7caa49c 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -135,7 +135,7 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas case apiHosts of [] -> action connections ((apiHost, peerId) : rest) -> do - withConnectionToNodeHost tracer peerId apiHost False $ \con -> do + withConnectionToNodeHost tracer peerId apiHost (Just "/?history=no") $ \con -> do withHydraClientConnections tracer rest (con : connections) action returnFaucetFunds tracer node cKeys = do diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index b7b1ee7ca10..fa520897e3a 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -310,7 +310,7 @@ withHydraNode tracer chainConfig workDir hydraNodeId hydraSKey hydraVKeys allNod \_ err processHandle -> do race (checkProcessHasNotDied ("hydra-node (" <> show hydraNodeId <> ")") processHandle (Just err)) - (withConnectionToNode tracer hydraNodeId True action) + (withConnectionToNode tracer hydraNodeId action) <&> either absurd id where logFilePath = workDir "logs" "hydra-node-" <> show hydraNodeId <.> "log" @@ -402,15 +402,15 @@ withHydraNode' tracer chainConfig workDir hydraNodeId hydraSKey hydraVKeys allNo , i /= hydraNodeId ] -withConnectionToNode :: forall a. Tracer IO HydraNodeLog -> Int -> Bool -> (HydraClient -> IO a) -> IO a +withConnectionToNode :: forall a. Tracer IO HydraNodeLog -> Int -> (HydraClient -> IO a) -> IO a withConnectionToNode tracer hydraNodeId = - withConnectionToNodeHost tracer hydraNodeId Host{hostname, port} + withConnectionToNodeHost tracer hydraNodeId Host{hostname, port} Nothing where hostname = "127.0.0.1" port = fromInteger $ 4_000 + toInteger hydraNodeId -withConnectionToNodeHost :: forall a. Tracer IO HydraNodeLog -> Int -> Host -> Bool -> (HydraClient -> IO a) -> IO a -withConnectionToNodeHost tracer hydraNodeId apiHost@Host{hostname, port} showHistory action = do +withConnectionToNodeHost :: forall a. Tracer IO HydraNodeLog -> Int -> Host -> Maybe String -> (HydraClient -> IO a) -> IO a +withConnectionToNodeHost tracer hydraNodeId apiHost@Host{hostname, port} queryParams action = do connectedOnce <- newIORef False tryConnect connectedOnce (200 :: Int) where @@ -428,14 +428,15 @@ withConnectionToNodeHost tracer hydraNodeId apiHost@Host{hostname, port} showHis , Handler $ retryOrThrow (Proxy @HandshakeException) ] - historyMode = if showHistory then "/" else "/?history=no" + historyMode = fromMaybe "/" queryParams - doConnect connectedOnce = runClient (T.unpack hostname) (fromInteger . toInteger $ port) historyMode $ \connection -> do - atomicWriteIORef connectedOnce True - traceWith tracer (NodeStarted hydraNodeId) - res <- action $ HydraClient{hydraNodeId, apiHost, connection, tracer} - sendClose connection ("Bye" :: Text) - pure res + doConnect connectedOnce = runClient (T.unpack hostname) (fromInteger . toInteger $ port) historyMode $ + \connection -> do + atomicWriteIORef connectedOnce True + traceWith tracer (NodeStarted hydraNodeId) + res <- action $ HydraClient{hydraNodeId, apiHost, connection, tracer} + sendClose connection ("Bye" :: Text) + pure res hydraNodeProcess :: RunOptions -> CreateProcess hydraNodeProcess = proc "hydra-node" . toArgs From 6654d088a7a15ef250886978ed8adaef13037fe3 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 29 Aug 2024 12:47:19 +0200 Subject: [PATCH 114/115] Fix not seeding during scenario but before it gets executed The reason is depends on before starting the hydra-cluster. That consumes the Faucet UTxO making the fundingTransaction from dataset invalid, thus making the scenario to fail. --- hydra-cluster/bench/Bench/EndToEnd.hs | 32 ++++++++------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/hydra-cluster/bench/Bench/EndToEnd.hs b/hydra-cluster/bench/Bench/EndToEnd.hs index da8d7caa49c..d9a77ee7f18 100644 --- a/hydra-cluster/bench/Bench/EndToEnd.hs +++ b/hydra-cluster/bench/Bench/EndToEnd.hs @@ -44,20 +44,7 @@ import Hydra.Ledger (txId) import Hydra.Logging (Tracer, traceWith, withTracerOutputTo) import Hydra.Network (Host) import Hydra.Party (Party, deriveParty) -import HydraNode ( - HydraClient, - hydraNodeId, - input, - output, - requestCommitTx, - send, - waitFor, - waitForAllMatch, - waitForNodesConnected, - waitMatch, - withConnectionToNodeHost, - withHydraCluster, - ) +import HydraNode (HydraClient, HydraNodeLog, hydraNodeId, input, output, requestCommitTx, send, waitFor, waitForAllMatch, waitForNodesConnected, waitMatch, withConnectionToNodeHost, withHydraCluster) import System.Directory (findExecutable) import System.FilePath (()) import System.IO (hGetLine, hPutStrLn) @@ -93,6 +80,8 @@ bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets} = do let parties = Set.fromList (deriveParty <$> hydraKeys) withOSStats workDir $ withCardanoNodeDevnet (contramap FromCardanoNode tracer) workDir $ \node@RunningNode{nodeSocket} -> do + putTextLn "Seeding network" + seedNetwork node dataset (contramap FromFaucet tracer) putTextLn "Publishing hydra scripts" hydraScriptsTxId <- publishHydraScriptsAs node Faucet putStrLn $ "Starting hydra cluster in " <> workDir @@ -101,7 +90,7 @@ bench startingNodeId timeoutSeconds workDir dataset@Dataset{clientDatasets} = do withHydraCluster hydraTracer workDir nodeSocket startingNodeId cardanoKeys hydraKeys hydraScriptsTxId contestationPeriod $ \(leader :| followers) -> do let clients = leader : followers waitForNodesConnected hydraTracer 20 clients - scenario tracer node workDir dataset parties leader followers + scenario hydraTracer node workDir dataset parties leader followers benchDemo :: NetworkId -> @@ -122,6 +111,8 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas Nothing -> error ("Not found running node at socket: " <> show nodeSocket <> ", and network: " <> show networkId) Just node -> do + putTextLn "Seeding network" + seedNetwork node dataset (contramap FromFaucet tracer) let clientSks = clientKeys <$> clientDatasets (`finally` returnFaucetFunds tracer node clientSks) $ do putStrLn $ "Connecting to hydra cluster: " <> show hydraClients @@ -129,7 +120,7 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas withHydraClientConnections hydraTracer (hydraClients `zip` [1 ..]) [] $ \case [] -> error "no hydra clients provided" (leader : followers) -> - scenario tracer node workDir dataset mempty leader followers + scenario hydraTracer node workDir dataset mempty leader followers where withHydraClientConnections tracer apiHosts connections action = do case apiHosts of @@ -150,7 +141,7 @@ benchDemo networkId nodeSocket timeoutSeconds hydraClients workDir dataset@Datas senders scenario :: - Tracer IO EndToEndLog -> + Tracer IO HydraNodeLog -> RunningNode -> FilePath -> Dataset -> @@ -158,12 +149,7 @@ scenario :: HydraClient -> [HydraClient] -> IO Summary -scenario tracer node workDir dataset@Dataset{clientDatasets, title, description} parties leader followers = do - let hydraTracer = contramap FromHydraNode tracer - - putTextLn "Seeding network" - seedNetwork node dataset (contramap FromFaucet tracer) - +scenario hydraTracer node workDir Dataset{clientDatasets, title, description} parties leader followers = do let clusterSize = fromIntegral $ length clientDatasets let clients = leader : followers let totalTxs = sum $ map (length . txSequence) clientDatasets From c5d12084872b52ace402b1c6bef5548a429a7740 Mon Sep 17 00:00:00 2001 From: Noon van der Silk Date: Thu, 29 Aug 2024 13:58:01 +0100 Subject: [PATCH 115/115] Don't call --random on pumba; note about time --- .github/workflows/network/run_pumba.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/network/run_pumba.sh b/.github/workflows/network/run_pumba.sh index d881f5e2ecf..06ee7549aa0 100755 --- a/.github/workflows/network/run_pumba.sh +++ b/.github/workflows/network/run_pumba.sh @@ -7,7 +7,9 @@ percent=$2 rest_node_names=$3 # Build Pumba netem command -nix_command="nix run github:noonio/pumba/noon/add-flake -- -l debug --random netem --duration 20m" +# Note: We leave it for 20 minutes; but really it's effectively unlimited. We don't +# expect any of our tests to run longer than that. +nix_command="nix run github:noonio/pumba/noon/add-flake -- -l debug netem --duration 20m" while IFS= read -r network; do nix_command+=" --target $network" @@ -18,4 +20,4 @@ nix_command+=" loss --percent \"$percent\" \"re2:$target_node_name\" &" echo "$nix_command" # Run Pumba netem command -eval "$nix_command" \ No newline at end of file +eval "$nix_command"