Skip to content

Commit c4d071a

Browse files
committed
cardano-testnet | Wait for blocks using foldEpochState instead of filtering logs
1 parent 98dc72f commit c4d071a

File tree

2 files changed

+46
-41
lines changed

2 files changed

+46
-41
lines changed

cardano-testnet/src/Testnet/Property/Assert.hs

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{-# LANGUAGE DuplicateRecordFields #-}
22
{-# LANGUAGE FlexibleContexts #-}
33
{-# LANGUAGE GADTs #-}
4-
{-# LANGUAGE NamedFieldPuns #-}
54
{-# LANGUAGE NumericUnderscores #-}
65
{-# LANGUAGE OverloadedStrings #-}
76
{-# LANGUAGE TypeApplications #-}
@@ -10,7 +9,6 @@
109
module Testnet.Property.Assert
1110
( assertByDeadlineIOCustom
1211
, readJsonLines
13-
, assertChainExtended
1412
, getRelevantSlots
1513
, assertExpectedSposInLedgerState
1614
, assertErasEqual
@@ -39,31 +37,24 @@ import qualified Data.Time.Clock as DTC
3937
import Data.Type.Equality
4038
import Data.Word (Word8)
4139
import GHC.Stack as GHC
42-
import RIO (throwString)
4340

4441
import Testnet.Process.RunIO
4542
import Testnet.Start.Types
46-
import Testnet.Types
4743

4844
import Hedgehog (MonadTest)
4945
import qualified Hedgehog as H
46+
import qualified Hedgehog.Extras as H
5047
import Hedgehog.Extras.Internal.Test.Integration (IntegrationState)
51-
import qualified Hedgehog.Extras.Stock.IO.File as IO
52-
import qualified Hedgehog.Extras.Test.Base as H
5348
import Hedgehog.Extras.Test.Process (ExecConfig)
5449

50+
import RIO (throwString)
51+
5552
newlineBytes :: Word8
5653
newlineBytes = 10
5754

5855
readJsonLines :: (MonadTest m, MonadIO m, HasCallStack) => FilePath -> m [Value]
5956
readJsonLines fp = withFrozenCallStack $ mapMaybe (Aeson.decode @Value) . LBS.split newlineBytes <$> H.evalIO (LBS.readFile fp)
6057

61-
fileJsonGrep :: FilePath -> (Value -> Bool) -> IO Bool
62-
fileJsonGrep fp f = do
63-
lines <- LBS.split newlineBytes <$> LBS.readFile fp
64-
let jsons = mapMaybe (Aeson.decode @Value) lines
65-
return $ L.any f jsons
66-
6758
assertByDeadlineIOCustom
6859
:: (MonadIO m, HasCallStack)
6960
=> String -> DTC.UTCTime -> IO Bool -> m ()
@@ -94,31 +85,17 @@ assertExpectedSposInLedgerState output (NumPools numExpectedPools) execConfig =
9485

9586
ePoolSet <- liftIOAnnotated (Aeson.eitherDecodeFileStrict' @(Set PoolId) output)
9687
case ePoolSet of
97-
Left err ->
88+
Left err ->
9889
throwString $ "Failed to decode stake pools from ledger state: " <> err
9990
Right poolSet -> do
10091
let numPoolsInLedgerState = Set.size poolSet
10192
unless (numPoolsInLedgerState == numExpectedPools) $
102-
throwString $ unlines
93+
throwString $ unlines
10394
[ "Expected number of stake pools not found in ledger state"
10495
, "Expected: ", show numExpectedPools
10596
, "Actual: ", show numPoolsInLedgerState
10697
]
10798

108-
assertChainExtended
109-
:: HasCallStack
110-
=> MonadIO m
111-
=> DTC.UTCTime
112-
-> NodeLoggingFormat
113-
-> TestnetNode
114-
-> m ()
115-
assertChainExtended deadline nodeLoggingFormat TestnetNode{nodeName, nodeStdout} = withFrozenCallStack $
116-
assertByDeadlineIOCustom ("Chain not extended in " <> nodeName) deadline $ do
117-
case nodeLoggingFormat of
118-
NodeLoggingFormatAsText -> IO.fileContains "Chain extended, new tip" nodeStdout
119-
NodeLoggingFormatAsJson -> fileJsonGrep nodeStdout $ \v ->
120-
Aeson.parseMaybe (Aeson.parseJSON @(LogEntry Kind)) v == Just (LogEntry (Kind "AddedToCurrentChain"))
121-
12299
newtype LogEntry a = LogEntry
123100
{ unLogEntry :: a
124101
} deriving (Eq, Show)

cardano-testnet/src/Testnet/Start/Cardano.hs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ import Testnet.Filepath
6565
import Testnet.Handlers (interruptNodesOnSigINT)
6666
import Testnet.Orphans ()
6767
import Testnet.Process.RunIO (execCli', execCli_, liftIOAnnotated, mkExecConfig)
68-
import Testnet.Property.Assert (assertChainExtended, assertExpectedSposInLedgerState)
68+
import Testnet.Property.Assert (assertExpectedSposInLedgerState)
6969
import Testnet.Runtime as TR
7070
import Testnet.Start.Types
7171
import Testnet.Types as TR hiding (shelleyGenesis)
@@ -74,8 +74,9 @@ import qualified Hedgehog.Extras as H
7474
import qualified Hedgehog.Extras.Stock.IO.Network.Port as H
7575
import Hedgehog.Internal.Property (failException)
7676

77-
import RIO (MonadUnliftIO, RIO (..), runRIO, throwString)
77+
import RIO (MonadUnliftIO, RIO (..), runRIO, throwString, timeout)
7878
import RIO.Orphans (ResourceMap)
79+
import RIO.State (put)
7980
import UnliftIO.Async
8081
import UnliftIO.Exception (stringException)
8182

@@ -226,8 +227,7 @@ cardanoTestnet
226227
, updateTimestamps
227228
} = do
228229
let CardanoTestnetOptions
229-
{ cardanoNodeLoggingFormat=nodeLoggingFormat
230-
, cardanoEnableNewEpochStateLogging=enableNewEpochStateLogging
230+
{ cardanoEnableNewEpochStateLogging=enableNewEpochStateLogging
231231
, cardanoNodes
232232
} = testnetOptions
233233
nPools = cardanoNumPools testnetOptions
@@ -282,7 +282,7 @@ cardanoTestnet
282282
liftIOAnnotated $ writeFile (nodeDataDir </> "port") (show portNumber)
283283
let topologyPath = tmpAbsPath </> Defaults.defaultNodeDataDir i </> "topology.json"
284284
tBytes <- liftIOAnnotated $ LBS.readFile topologyPath
285-
case eitherDecode tBytes of
285+
case eitherDecode tBytes of
286286
Right (abstractTopology :: P2P.NetworkTopology NodeId) -> do
287287
topology <- mapM idToRemoteAddressP2P abstractTopology
288288
liftIOAnnotated $ LBS.writeFile topologyPath $ encode topology
@@ -349,11 +349,8 @@ cardanoTestnet
349349
-- Interrupt cardano nodes when the main process is interrupted
350350
liftIOAnnotated $ interruptNodesOnSigINT testnetNodes'
351351

352-
-- FIXME: use foldEpochState waiting for chain extensions
353-
now <- liftIOAnnotated DTC.getCurrentTime
354-
let deadline = DTC.addUTCTime 45 now
355-
forM_ testnetNodes' $ \nodeStdoutFile -> do
356-
assertChainExtended deadline nodeLoggingFormat nodeStdoutFile
352+
-- Make sure that all nodes are healthy by waiting for a chain extension
353+
mapConcurrently_ (waitForBlockThrow 45 (File nodeConfigFile)) testnetNodes'
357354

358355
let runtime = TestnetRuntime
359356
{ configurationFile = File nodeConfigFile
@@ -397,6 +394,37 @@ cardanoTestnet
397394
mkTestnetNodeKeyPaths :: Int -> SpoNodeKeys
398395
mkTestnetNodeKeyPaths n = makePathsAbsolute $ Defaults.defaultSpoKeys n
399396

397+
-- wait for new blocks or throw an exception if there are none in the timeout period
398+
waitForBlockThrow :: MonadUnliftIO m
399+
=> MonadCatch m
400+
=> Int -- ^ timeout in seconds
401+
-> NodeConfigFile 'In
402+
-> TestnetNode
403+
-> m ()
404+
waitForBlockThrow timeoutSeconds nodeConfigFile node@TestnetNode{nodeName} = do
405+
result <- timeout (timeoutSeconds * 1_000_000) $
406+
runExceptT . foldEpochState
407+
nodeConfigFile
408+
(nodeSocketPath node)
409+
QuickValidation
410+
(EpochNo maxBound)
411+
minBound
412+
$ \_ slotNo blockNo -> do
413+
put slotNo
414+
pure $ if blockNo >= 1
415+
then ConditionMet -- we got one block
416+
else ConditionNotMet
417+
418+
case result of
419+
Just (Right (ConditionMet, _)) -> pure ()
420+
Just (Right (ConditionNotMet, slotNo)) ->
421+
throwString $ nodeName <> " was unable to produce any blocks. Reached slot " <> show slotNo
422+
Just (Left err) ->
423+
throwString $ "foldBlocks on " <> nodeName <> " encountered an error while producing blocks: " <> show (prettyError err)
424+
_ ->
425+
throwString $ nodeName <> " was unable to produce any blocks for " <> show timeoutSeconds <> "s"
426+
427+
400428
-- | A convenience wrapper around `createTestnetEnv` and `cardanoTestnet`
401429
createAndRunTestnet :: ()
402430
=> HasCallStack
@@ -420,8 +448,8 @@ retryOnAddressInUseError
420448
retryOnAddressInUseError act = withFrozenCallStack $ go maximumTimeout retryTimeout
421449
where
422450
go :: HasCallStack => NominalDiffTime -> NominalDiffTime -> ExceptT NodeStartFailure m a
423-
go timeout interval
424-
| timeout <= 0 = withFrozenCallStack $ do
451+
go timeout' interval
452+
| timeout' <= 0 = withFrozenCallStack $ do
425453
act
426454
| otherwise = withFrozenCallStack $ do
427455
!time <- liftIOAnnotated DTC.getCurrentTime
@@ -430,7 +458,7 @@ retryOnAddressInUseError act = withFrozenCallStack $ go maximumTimeout retryTime
430458
liftIOAnnotated $ threadDelay (round $ interval * 1_000_000)
431459
!time' <- liftIOAnnotated DTC.getCurrentTime
432460
let elapsedTime = time' `diffUTCTime` time
433-
newTimeout = timeout - elapsedTime
461+
newTimeout = timeout' - elapsedTime
434462
go newTimeout interval
435463
e -> throwError e
436464

0 commit comments

Comments
 (0)