diff --git a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs index 32655d5b5ca..de3312b4ee8 100644 --- a/hydra-cluster/src/Hydra/Cluster/Scenarios.hs +++ b/hydra-cluster/src/Hydra/Cluster/Scenarios.hs @@ -21,7 +21,7 @@ import Control.Concurrent.Async (mapConcurrently_) import Control.Lens ((^..), (^?)) import Data.Aeson (Value, object, (.=)) import Data.Aeson qualified as Aeson -import Data.Aeson.Lens (key, values, _JSON) +import Data.Aeson.Lens (key, values, _JSON, _String) import Data.Aeson.Types (parseMaybe) import Data.ByteString (isInfixOf) import Data.ByteString qualified as B @@ -469,6 +469,44 @@ singlePartyCommitsScriptBlueprint tracer workDir node hydraScriptsTxId = RunningNode{networkId, nodeSocket, blockTime} = node +persistenceCanLoadWithEmptyCommit :: + Tracer IO EndToEndLog -> + FilePath -> + RunningNode -> + TxId -> + IO () +persistenceCanLoadWithEmptyCommit tracer workDir node hydraScriptsTxId = + (`finally` returnFundsToFaucet tracer node Alice) $ do + refuelIfNeeded tracer node Alice 20_000_000 + aliceChainConfig <- chainConfigFor Alice workDir nodeSocket hydraScriptsTxId [] $ UnsafeContestationPeriod 100 + let hydraNodeId = 1 + let hydraTracer = contramap FromHydraNode tracer + headId <- withHydraNode hydraTracer aliceChainConfig workDir hydraNodeId aliceSk [] [1] $ \n1 -> do + send n1 $ input "Init" [] + headId <- waitMatch (10 * blockTime) n1 $ headIsInitializingWith (Set.fromList [alice]) + + requestCommitTx n1 mempty >>= submitTx node + waitFor hydraTracer (10 * blockTime) [n1] $ + output "HeadIsOpen" ["utxo" .= object mempty, "headId" .= headId] + pure headId + let persistenceState = workDir "state-" <> show hydraNodeId "state" + stateContents <- readFileBS persistenceState + let headOpened = BSC.pack $ List.last (List.lines $ BSC.unpack stateContents) + case headOpened ^? key "stateChanged" . key "tag" . _String of + Nothing -> error "Failed to find HeadIsOpened in the state file" + Just headIsOpen -> do + headIsOpen `shouldBe` "HeadOpened" + withHydraNode hydraTracer aliceChainConfig workDir hydraNodeId aliceSk [] [1] $ \n1 -> do + waitFor hydraTracer (10 * blockTime) [n1] $ + output "HeadIsOpen" ["utxo" .= object mempty, "headId" .= headId] + + send n1 $ input "GetUTxO" [] + + waitFor hydraTracer 10 [n1] $ + output "GetUTxOResponse" ["headId" .= headId, "utxo" .= (mempty :: UTxO)] + where + RunningNode{nodeSocket, blockTime} = node + -- | Single hydra-node where the commit is done from a raw transaction -- blueprint. singlePartyCommitsFromExternalTxBlueprint :: diff --git a/hydra-cluster/test/Test/EndToEndSpec.hs b/hydra-cluster/test/Test/EndToEndSpec.hs index 1d613524e07..c29e92b6dd8 100644 --- a/hydra-cluster/test/Test/EndToEndSpec.hs +++ b/hydra-cluster/test/Test/EndToEndSpec.hs @@ -64,6 +64,7 @@ import Hydra.Cluster.Scenarios ( canSubmitTransactionThroughAPI, headIsInitializingWith, initWithWrongKeys, + persistenceCanLoadWithEmptyCommit, refuelIfNeeded, restartedNodeCanAbort, restartedNodeCanObserveCommitTx, @@ -217,6 +218,11 @@ spec = around (showLogsOnFailure "EndToEndSpec") $ do withCardanoNodeDevnet (contramap FromCardanoNode tracer) tmpDir $ \node -> publishHydraScriptsAs node Faucet >>= singlePartyCommitsScriptBlueprint tracer tmpDir node + it "persistence can load with empty commit" $ \tracer -> do + withClusterTempDir $ \tmpDir -> do + withCardanoNodeDevnet (contramap FromCardanoNode tracer) tmpDir $ \node -> + publishHydraScriptsAs node Faucet + >>= persistenceCanLoadWithEmptyCommit tracer tmpDir node describe "three hydra nodes scenario" $ do it "does not error when all nodes open the head concurrently" $ \tracer ->