11{-# LANGUAGE TypeApplications #-}
2+ {-# OPTIONS_GHC -Wno-unused-matches #-}
23
34module Hydra.BehaviorSpec where
45
5- import Cardano.Prelude hiding (atomically , check , threadDelay )
6- import Control.Monad.Class.MonadSTM (TVar , atomically , check , modifyTVar , newTVarIO , readTVar )
7- import Control.Monad.Class.MonadTime (DiffTime )
8- import Control.Monad.Class.MonadTimer (threadDelay )
9- import Data.IORef (modifyIORef' , newIORef , readIORef )
6+ import Cardano.Prelude hiding (Async , STM , async , atomically , cancel , check , link , poll , threadDelay )
7+ import Control.Monad.Class.MonadAsync (MonadAsync , async , cancel , link , poll )
8+ import Control.Monad.Class.MonadFork (MonadFork )
9+ import Control.Monad.Class.MonadSTM (
10+ MonadSTM ,
11+ TVar ,
12+ atomically ,
13+ check ,
14+ modifyTVar ,
15+ modifyTVar' ,
16+ newEmptyTMVar ,
17+ newTVarIO ,
18+ putTMVar ,
19+ readTVar ,
20+ takeTMVar ,
21+ )
22+ import Control.Monad.Class.MonadThrow (MonadMask )
23+ import Control.Monad.Class.MonadTimer (DiffTime , MonadTimer , threadDelay , timeout )
1024import Hydra.HeadLogic (
1125 ClientRequest (.. ),
1226 ClientResponse (.. ),
@@ -19,8 +33,7 @@ import Hydra.HeadLogic (
1933 )
2034import Hydra.Ledger (LedgerState )
2135import Hydra.Ledger.Mock (MockTx (.. ), mockLedger )
22-
23- import Hydra.Logging (traceInTVarIO )
36+ import Hydra.Logging (traceInTVar )
2437import Hydra.Network (HydraNetwork (.. ))
2538import Hydra.Node (
2639 HydraNode (.. ),
@@ -34,17 +47,16 @@ import Hydra.Node (
3447 queryLedgerState ,
3548 runHydraNode ,
3649 )
37- import System.Timeout (timeout )
3850import Test.Hspec (
3951 Spec ,
4052 describe ,
41- expectationFailure ,
4253 it ,
4354 shouldContain ,
4455 shouldNotBe ,
4556 shouldReturn ,
4657 )
4758import Test.Util (failAfter )
59+ import Prelude (error )
4860
4961spec :: Spec
5062spec = describe " Behavior of one ore more hydra-nodes" $ do
@@ -64,7 +76,7 @@ spec = describe "Behavior of one ore more hydra-nodes" $ do
6476 sendRequest n (Init [1 ]) `shouldReturn` ()
6577
6678 it " accepts Commit after successful Init" $ do
67- n <- simulatedChainAndNetwork >>= startHydraNode 1
79+ n :: HydraProcess IO MockTx <- simulatedChainAndNetwork >>= startHydraNode 1
6880 sendRequest n (Init [1 ])
6981 sendRequest n (Commit 1 )
7082
@@ -217,7 +229,7 @@ data HydraProcess m tx = HydraProcess
217229 , capturedLogs :: TVar m [HydraNodeLog tx ]
218230 }
219231
220- data Connections = Connections { chain :: OnChain IO , network :: HydraNetwork MockTx IO }
232+ data Connections m = Connections { chain :: OnChain m , network :: HydraNetwork MockTx m }
221233
222234-- | Creates a simulated chain by returning a function to create the chain
223235-- client interface for a node. This is necessary, to get to know all nodes
@@ -226,19 +238,25 @@ data Connections = Connections {chain :: OnChain IO, network :: HydraNetwork Moc
226238-- NOTE: This implementation currently ensures that no two equal 'OnChainTx' can
227239-- be posted on chain assuming the construction of the real transaction is
228240-- referentially transparent.
229- simulatedChainAndNetwork :: IO (HydraNode MockTx IO -> IO Connections )
241+ simulatedChainAndNetwork :: MonadSTM m => m (HydraNode MockTx m -> m ( Connections m ) )
230242simulatedChainAndNetwork = do
231- refHistory <- newIORef []
243+ refHistory <- newTVarIO []
232244 nodes <- newTVarIO []
233245 pure $ \ n -> do
234246 atomically $ modifyTVar nodes (n : )
235247 pure $ Connections OnChain {postTx = postTx nodes refHistory} HydraNetwork {broadcast = broadcast nodes}
236248 where
237249 postTx nodes refHistory tx = do
238- h <- readIORef refHistory
239- unless (tx `elem` h) $ do
240- modifyIORef' refHistory (tx : )
241- atomically (readTVar nodes) >>= mapM_ (`handleChainTx` tx)
250+ res <- atomically $ do
251+ h <- readTVar refHistory
252+ if tx `elem` h
253+ then pure Nothing
254+ else do
255+ modifyTVar' refHistory (tx : )
256+ Just <$> readTVar nodes
257+ case res of
258+ Nothing -> pure ()
259+ Just ns -> mapM_ (`handleChainTx` tx) ns
242260
243261 broadcast nodes msg = atomically (readTVar nodes) >>= mapM_ (`handleMessage` msg)
244262
@@ -247,21 +265,24 @@ testContestationPeriod :: DiffTime
247265testContestationPeriod = 10
248266
249267startHydraNode ::
268+ (MonadAsync m , MonadTimer m , MonadFork m , MonadMask m ) =>
250269 Natural ->
251- (HydraNode MockTx IO -> IO Connections ) ->
252- IO (HydraProcess IO MockTx )
270+ (HydraNode MockTx m -> m ( Connections m ) ) ->
271+ m (HydraProcess m MockTx )
253272startHydraNode = startHydraNode' NoSnapshots
254273
255274startHydraNode' ::
275+ (MonadAsync m , MonadTimer m , MonadFork m , MonadMask m ) =>
256276 SnapshotStrategy ->
257277 Natural ->
258- (HydraNode MockTx IO -> IO Connections ) ->
259- IO (HydraProcess IO MockTx )
278+ (HydraNode MockTx m -> m ( Connections m ) ) ->
279+ m (HydraProcess m MockTx )
260280startHydraNode' snapshotStrategy nodeId connectToChain = do
261281 capturedLogs <- newTVarIO []
262- response <- newEmptyMVar
282+ response <- atomically newEmptyTMVar
263283 node <- createHydraNode response
264- nodeThread <- async $ runHydraNode (traceInTVarIO capturedLogs) node
284+ -- TODO(SN): trace directly into io-sim's 'Trace'
285+ nodeThread <- async $ runHydraNode (traceInTVar capturedLogs) node
265286 link nodeThread
266287 pure $
267288 HydraProcess
@@ -271,27 +292,25 @@ startHydraNode' snapshotStrategy nodeId connectToChain = do
271292 Nothing -> pure Ready
272293 Just _ -> pure NotReady
273294 , sendRequest = handleClientRequest node
274- , waitForResponse = takeMVar response
295+ , waitForResponse = atomically $ takeTMVar response
275296 , waitForLedgerState =
276297 \ st -> do
277- result <-
278- timeout
279- 1_000_000
280- ( atomically $ do
281- st' <- queryLedgerState node
282- check (st == st')
283- )
284- when (isNothing result) $ expectationFailure (" Expected ledger state of node " <> show nodeId <> " to be " <> show st)
298+ result <- timeout 1 $
299+ atomically $ do
300+ st' <- queryLedgerState node
301+ check (st == st')
302+ -- TODO(SN): use MonadThrow instead?
303+ when (isNothing result) $ error (" Expected ledger state of node " <> show nodeId <> " to be " <> show st)
285304 , nodeId
286305 , capturedLogs
287306 }
288307 where
289308 createHydraNode response = do
290309 let env = Environment nodeId
291310 eq <- createEventQueue
292- let headState = createHeadState [] (HeadParameters testContestationPeriod mempty ) SnapshotStrategy
311+ let headState = createHeadState [] (HeadParameters testContestationPeriod mempty ) snapshotStrategy
293312 hh <- createHydraHead headState mockLedger
294313 let hn' = HydraNetwork {broadcast = const $ pure () }
295- let node = HydraNode {eq, hn = hn', hh, oc = OnChain (const $ pure () ), sendResponse = putMVar response, env}
314+ let node = HydraNode {eq, hn = hn', hh, oc = OnChain (const $ pure () ), sendResponse = atomically . putTMVar response, env}
296315 Connections oc hn <- connectToChain node
297316 pure node{oc, hn}
0 commit comments