@@ -14,7 +14,7 @@ module Testnet.Components.Query
1414 , getEpochState
1515 , getSlotNumber
1616 , getBlockNumber
17- , watchEpochStateUpdate
17+ , getEpochStateDetails
1818
1919 , getMinDRepDeposit
2020 , getMinGovActionDeposit
@@ -60,10 +60,8 @@ import qualified Cardano.Ledger.State as L
6060
6161import Prelude
6262
63- import Control.Exception.Safe (MonadCatch )
6463import Control.Monad
6564import Control.Monad.Trans.Resource
66- import Control.Monad.Trans.State.Strict (put )
6765import Data.IORef
6866import Data.List (sortOn )
6967import qualified Data.Map as Map
@@ -123,7 +121,7 @@ waitForEpochs
123121 -> EpochInterval -- ^ Number of epochs to wait
124122 -> m EpochNo -- ^ The epoch number reached
125123waitForEpochs epochStateView interval = withFrozenCallStack $ do
126- void $ watchEpochStateUpdate epochStateView interval $ \ _ -> pure Nothing
124+ void . retryUntilRightM epochStateView ( WaitForEpochs interval) . pure $ Left ()
127125 getCurrentEpochNo epochStateView
128126
129127-- | Wait for the requested number of blocks
@@ -132,20 +130,15 @@ waitForBlocks
132130 => MonadIO m
133131 => MonadTest m
134132 => MonadAssertion m
135- => MonadCatch m
136133 => EpochStateView
137134 -> Word64 -- ^ Number of blocks to wait
138135 -> m BlockNo -- ^ The block number reached
139136waitForBlocks epochStateView numberOfBlocks = withFrozenCallStack $ do
140137 BlockNo startingBlockNumber <- getBlockNumber epochStateView
141138 H. note_ $ " Current block number: " <> show startingBlockNumber <> " . "
142139 <> " Waiting for " <> show numberOfBlocks <> " blocks"
143- H. noteShowM . H. nothingFailM . fmap (fmap BlockNo ) $
144- watchEpochStateUpdate epochStateView (EpochInterval maxBound ) $ \ (_, _, BlockNo blockNumber) ->
145- pure $
146- if blockNumber >= startingBlockNumber + numberOfBlocks
147- then Just blockNumber
148- else Nothing
140+ void . retryUntilRightM epochStateView (WaitForBlocks numberOfBlocks) . pure $ Left ()
141+ getBlockNumber epochStateView
149142
150143data TestnetWaitPeriod
151144 = WaitForEpochs EpochInterval
@@ -159,6 +152,34 @@ instance Show TestnetWaitPeriod where
159152 WaitForBlocks n -> " WaitForBlocks " <> show n
160153 WaitForSlots n -> " WaitForSlots " <> show n
161154
155+ -- | Core retry loop. Repeats the action every 300ms until it returns 'Right'
156+ -- or the timeout is reached, in which case the last 'Left' is returned.
157+ retryUntilRightM
158+ :: HasCallStack
159+ => MonadIO m
160+ => MonadTest m
161+ => MonadAssertion m
162+ => EpochStateView
163+ -> TestnetWaitPeriod
164+ -> m (Either e a )
165+ -> m (Either e a )
166+ retryUntilRightM esv timeout act = withFrozenCallStack $ do
167+ startingValue <- getCurrentValue
168+ go $ startingValue + timeoutW64
169+ where
170+ go deadline = act >>= \ case
171+ r@ (Right _) -> pure r
172+ l@ (Left _) -> do
173+ cv <- getCurrentValue
174+ if cv > deadline
175+ then pure l
176+ else H. threadDelay 300_000 >> go deadline
177+
178+ (getCurrentValue, timeoutW64) = case timeout of
179+ WaitForEpochs (EpochInterval n) -> (unEpochNo <$> getCurrentEpochNo esv, fromIntegral n)
180+ WaitForSlots n -> (unSlotNo <$> getSlotNumber esv, n)
181+ WaitForBlocks n -> (unBlockNo <$> getBlockNumber esv, n)
182+
162183-- | Retries the action until it returns 'Just' or the timeout is reached
163184retryUntilJustM
164185 :: HasCallStack
@@ -169,32 +190,12 @@ retryUntilJustM
169190 -> TestnetWaitPeriod -- ^ timeout for an operation
170191 -> m (Maybe a )
171192 -> m a
172- retryUntilJustM esv timeout act = withFrozenCallStack $ do
173- startingValue <- getCurrentValue
174- go startingValue
175- where
176- go startingValue = withFrozenCallStack $ do
177- cv <- getCurrentValue
178- when (timeoutW64 + startingValue < cv) $ do
179- H. note_ $ " Action did not result in 'Just' - waited for: " <> show timeout
180- H. failure
181- act >>= \ case
182- Just a -> pure a
183- Nothing -> do
184- H. threadDelay 300_000
185- go startingValue
186-
187- getCurrentValue = withFrozenCallStack $
188- case timeout of
189- WaitForEpochs _ -> unEpochNo <$> getCurrentEpochNo esv
190- WaitForSlots _ -> unSlotNo <$> getSlotNumber esv
191- WaitForBlocks _ -> unBlockNo <$> getBlockNumber esv
192-
193- timeoutW64 =
194- case timeout of
195- WaitForEpochs (EpochInterval n) -> fromIntegral n
196- WaitForSlots n -> n
197- WaitForBlocks n -> n
193+ retryUntilJustM esv timeout act = withFrozenCallStack $
194+ retryUntilRightM esv timeout (maybe (Left () ) Right <$> act) >>= \ case
195+ Right a -> pure a
196+ Left () -> do
197+ H. note_ $ " Action did not result in 'Just' - waited for: " <> show timeout
198+ H. failure
198199
199200-- | Like 'retryUntilJustM' but takes a plain action and a predicate instead of
200201-- an action returning 'Maybe'. On timeout, annotates the last value that failed
@@ -210,34 +211,13 @@ retryUntilM
210211 -> m a -- ^ action to retry
211212 -> (a -> Bool ) -- ^ predicate that must hold
212213 -> m a
213- retryUntilM esv timeout act predicate = withFrozenCallStack $ do
214- startingValue <- getCurrentValue
215- go startingValue
216- where
217- go startingValue = withFrozenCallStack $ do
218- result <- act
219- if predicate result
220- then pure result
221- else do
222- cv <- getCurrentValue
223- if timeoutW64 + startingValue < cv
224- then do
225- H. noteShow_ result
226- H. note_ $ " Predicate not satisfied after: " <> show timeout
227- H. failure
228- else H. threadDelay 300_000 >> go startingValue
229-
230- getCurrentValue = withFrozenCallStack $
231- case timeout of
232- WaitForEpochs _ -> unEpochNo <$> getCurrentEpochNo esv
233- WaitForSlots _ -> unSlotNo <$> getSlotNumber esv
234- WaitForBlocks _ -> unBlockNo <$> getBlockNumber esv
235-
236- timeoutW64 =
237- case timeout of
238- WaitForEpochs (EpochInterval n) -> fromIntegral n
239- WaitForSlots n -> n
240- WaitForBlocks n -> n
214+ retryUntilM esv timeout act predicate = withFrozenCallStack $
215+ retryUntilRightM esv timeout ((\ r -> if predicate r then Right r else Left r) <$> act) >>= \ case
216+ Right a -> pure a
217+ Left r -> do
218+ H. noteShow_ r
219+ H. note_ $ " Predicate not satisfied after: " <> show timeout
220+ H. failure
241221
242222-- | Status of the 'EpochStateView' background thread when epoch state is not yet available
243223data EpochStateStatus
@@ -247,12 +227,8 @@ data EpochStateStatus
247227 -- ^ The background thread encountered an error while folding blocks
248228
249229-- | A read-only mutable pointer to an epoch state, updated automatically
250- data EpochStateView = EpochStateView
251- { nodeConfigPath :: ! (NodeConfigFile In )
252- -- ^ node configuration file path
253- , socketPath :: ! SocketPath
254- -- ^ node socket path, to which foldEpochState is connected to
255- , epochStateView :: ! (IORef (Either EpochStateStatus (AnyNewEpochState , SlotNo , BlockNo )))
230+ newtype EpochStateView = EpochStateView
231+ { epochStateView :: IORef (Either EpochStateStatus (AnyNewEpochState , SlotNo , BlockNo ))
256232 -- ^ Automatically updated current NewEpochState. 'Left' indicates the state is not yet available
257233 -- (either not initialised or an error occurred). 'Right' contains the latest epoch state.
258234 -- Use 'getEpochState', 'getBlockNumber', 'getSlotNumber' to access the values.
@@ -344,34 +320,7 @@ getEpochStateView nodeConfigFile socketPath = withFrozenCallStack $ do
344320 case result of
345321 Left err -> writeIORef epochStateView $ Left $ EpochStateFoldError err
346322 Right _ -> pure ()
347- pure $ EpochStateView nodeConfigFile socketPath epochStateView
348-
349- -- | Watch the epoch state view until the guard function returns 'Just' or the timeout epoch is reached.
350- -- Executes the guard function every 300ms. Waits for at most @maxWait@ epochs.
351- -- The function will return the result of the guard function if it is met within the number of epochs,
352- -- otherwise it will return @Nothing@.
353- watchEpochStateUpdate
354- :: forall m a . (HasCallStack , MonadIO m , MonadTest m , MonadAssertion m )
355- => EpochStateView -- ^ The info to access the epoch state
356- -> EpochInterval -- ^ The maximum number of epochs to wait
357- -> ((AnyNewEpochState , SlotNo , BlockNo ) -> m (Maybe a )) -- ^ The guard function (@Just@ if the condition is met, @Nothing@ otherwise)
358- -> m (Maybe a )
359- watchEpochStateUpdate epochStateView (EpochInterval maxWait) f = withFrozenCallStack $ do
360- AnyNewEpochState _ newEpochState _ <- getEpochState epochStateView
361- let EpochNo currentEpoch = L. nesEL newEpochState
362- go $ currentEpoch + fromIntegral maxWait
363- where
364- go :: Word64 -> m (Maybe a )
365- go timeout = do
366- newEpochStateDetails@ (AnyNewEpochState _ newEpochState' _, _, _) <- getEpochStateDetails epochStateView
367- let EpochNo currentEpoch = L. nesEL newEpochState'
368- f newEpochStateDetails >>= \ case
369- Just result -> pure (Just result)
370- Nothing
371- | currentEpoch > timeout -> pure Nothing
372- | otherwise -> do
373- H. threadDelay 300_000
374- go timeout
323+ pure $ EpochStateView epochStateView
375324
376325-- | Retrieve all UTxOs map from the epoch state view.
377326findAllUtxos
@@ -505,42 +454,18 @@ checkDRepState
505454 -> Maybe a ) -- ^ A function that checks whether the DRep state is correct or up to date
506455 -- and potentially inspects it.
507456 -> m a
508- checkDRepState epochStateView@ EpochStateView {nodeConfigPath, socketPath} sbe f = withFrozenCallStack $ do
509- currentEpoch <- getCurrentEpochNo epochStateView
510- let terminationEpoch = succ . succ $ currentEpoch
511- result <- H. evalIO . runExceptT $ foldEpochState nodeConfigPath socketPath QuickValidation terminationEpoch Nothing
512- $ \ (AnyNewEpochState actualEra newEpochState _) _slotNumber _blockNumber -> do
513- Refl <- either error pure $ assertErasEqual sbe actualEra
514- let dreps =
515- shelleyBasedEraConstraints sbe
516- $ SQ. queryDRepState newEpochState Set. empty
517- case f dreps of
518- Nothing -> pure ConditionNotMet
519- Just a -> do put $ Just a
520- pure ConditionMet
521- case result of
522- Left (FoldBlocksApplyBlockError (TerminationEpochReached epochNo)) -> do
523- H. note_ $ unlines
524- [ " checkDRepState: condition not met before termination epoch: " <> show epochNo
525- , " This is likely an error of this test." ]
526- H. failure
527- Left err -> do
528- H. note_ $ unlines
529- [ " checkDRepState: could not reach termination epoch: " <> docToString (prettyError err)
530- , " This is probably an error unrelated to this test." ]
457+ checkDRepState epochStateView sbe f = withFrozenCallStack $
458+ retryUntilRightM epochStateView (WaitForEpochs $ EpochInterval 2 ) action >>= \ case
459+ Right a -> pure a
460+ Left () -> do
461+ H. note_ " checkDRepState: condition not met within 2 epochs. This is likely a test error."
531462 H. failure
532- Right (_, Nothing ) -> do
533- H. note_ $ unlines
534- [ " checkDRepState: foldEpochState returned Nothing: "
535- , " This is probably an error related to foldEpochState." ]
536- H. failure
537- Right (ConditionNotMet , Just _) -> do
538- H. note_ $ unlines
539- [ " checkDRepState: foldEpochState returned Just and ConditionNotMet: "
540- , " This is probably an error related to foldEpochState." ]
541- H. failure
542- Right (ConditionMet , Just val) ->
543- return val
463+ where
464+ action = do
465+ AnyNewEpochState actualEra newEpochState _ <- getEpochState epochStateView
466+ Refl <- H. leftFail $ assertErasEqual sbe actualEra
467+ pure . maybe (Left () ) Right . f $ shelleyBasedEraConstraints sbe
468+ $ SQ. queryDRepState newEpochState Set. empty
544469
545470-- | Obtain governance state from node (CLI query)
546471getGovState
@@ -627,30 +552,21 @@ assertNewEpochState
627552 -- ^ The lens to access the specific value in the epoch state.
628553 -> value -- ^ The expected value to check in the epoch state.
629554 -> m ()
630- assertNewEpochState epochStateView sbe maxWait lens expected = withFrozenCallStack $ do
631- mStateView <- watchEpochStateUpdate epochStateView maxWait (const checkEpochState)
632- when (isNothing mStateView) $ do
633- val <- getFromEpochStateForEra
634- -- there's a tiny tiny chance that the value has changed since 'watchEpochStateUpdate'
635- -- so check it again
636- if val == expected
637- then pure ()
638- else H. failMessage callStack $ unlines
639- [ " assertNewEpochState: expected value not reached within the time frame."
640- , " Expected value: " <> show expected
641- , " Actual value: " <> show val
642- ]
555+ assertNewEpochState epochStateView sbe maxWait lens expected = withFrozenCallStack $
556+ retryUntilRightM epochStateView (WaitForEpochs maxWait) checkEpochState >>= \ case
557+ Right () -> pure ()
558+ Left actual -> do
559+ H. note_ $ unlines
560+ [ " assertNewEpochState: expected value not reached within " <> show maxWait
561+ , " Expected: " <> show expected
562+ , " Actual: " <> show actual
563+ ]
564+ H. failure
643565 where
644- checkEpochState
645- :: HasCallStack
646- => m (Maybe () )
647566 checkEpochState = withFrozenCallStack $ do
648567 val <- getFromEpochStateForEra
649- pure $ if val == expected then Just () else Nothing
568+ pure $ if val == expected then Right () else Left val
650569
651- getFromEpochStateForEra
652- :: HasCallStack
653- => m value
654570 getFromEpochStateForEra = withFrozenCallStack $ do
655571 (AnyNewEpochState actualEra newEpochState _, _, _) <- getEpochStateDetails epochStateView
656572 Refl <- H. leftFail $ assertErasEqual sbe actualEra
0 commit comments