11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE FlexibleContexts #-}
33{-# LANGUAGE GADTs #-}
4+ {-# LANGUAGE LambdaCase #-}
45{-# LANGUAGE NamedFieldPuns #-}
56{-# LANGUAGE NumericUnderscores #-}
67{-# LANGUAGE OverloadedStrings #-}
910
1011module Testnet.Components.Query
1112 ( EpochStateView
12- , checkDRepsNumber
13- , checkDRepState
13+ , getEpochStateView
1414 , getEpochState
15+ , getSlotNumber
16+ , getBlockNumber
17+ , watchEpochStateUpdate
18+
1519 , getMinDRepDeposit
1620 , getMinGovActionDeposit
1721 , getGovState
1822 , getCurrentEpochNo
19- , waitUntilEpoch
23+
24+ , TestnetWaitPeriod (.. )
2025 , waitForEpochs
21- , getEpochStateView
26+ , waitUntilEpoch
27+ , waitForBlocks
28+ , retryUntilJustM
29+
2230 , findAllUtxos
2331 , findUtxosWithAddress
2432 , findLargestUtxoWithAddress
2533 , findLargestUtxoForPaymentKey
34+
35+ , checkDRepsNumber
36+ , checkDRepState
2637 , assertNewEpochState
27- , watchEpochStateView
2838 ) where
2939
3040import Cardano.Api as Api
@@ -40,7 +50,7 @@ import qualified Cardano.Ledger.Shelley.LedgerState as L
4050import qualified Cardano.Ledger.UTxO as L
4151
4252import Control.Exception.Safe (MonadCatch )
43- import Control.Monad ( void )
53+ import Control.Monad
4454import Control.Monad.Trans.Resource
4555import Control.Monad.Trans.State.Strict (put )
4656import Data.Bifunctor (bimap )
@@ -49,11 +59,12 @@ import Data.List (sortOn)
4959import Data.Map.Strict (Map )
5060import qualified Data.Map.Strict as M
5161import qualified Data.Map.Strict as Map
52- import Data.Maybe ( listToMaybe )
62+ import Data.Maybe
5363import Data.Ord (Down (.. ))
5464import Data.Text (Text )
5565import qualified Data.Text as T
5666import Data.Type.Equality
67+ import Data.Word (Word64 )
5768import GHC.Exts (IsList (.. ))
5869import GHC.Stack
5970import Lens.Micro (Lens' , to , (^.) )
@@ -101,28 +112,135 @@ waitForEpochs
101112 -> EpochInterval -- ^ Number of epochs to wait
102113 -> m EpochNo -- ^ The epoch number reached
103114waitForEpochs epochStateView interval = withFrozenCallStack $ do
104- void $ watchEpochStateView epochStateView ( const $ pure Nothing ) interval
115+ void $ watchEpochStateUpdate epochStateView interval $ \ _ -> pure Nothing
105116 getCurrentEpochNo epochStateView
106117
118+ -- | Wait for the requested number of blocks
119+ waitForBlocks
120+ :: HasCallStack
121+ => MonadIO m
122+ => MonadTest m
123+ => MonadAssertion m
124+ => MonadCatch m
125+ => EpochStateView
126+ -> Word64 -- ^ Number of blocks to wait
127+ -> m BlockNo -- ^ The block number reached
128+ waitForBlocks epochStateView numberOfBlocks = withFrozenCallStack $ do
129+ BlockNo startingBlockNumber <- getBlockNumber epochStateView
130+ H. note_ $ " Current block number: " <> show startingBlockNumber <> " . "
131+ <> " Waiting for " <> show numberOfBlocks <> " blocks"
132+ H. noteShowM . H. nothingFailM . fmap (fmap BlockNo ) $
133+ watchEpochStateUpdate epochStateView (EpochInterval maxBound ) $ \ (_, _, BlockNo blockNumber) ->
134+ pure $
135+ if blockNumber >= startingBlockNumber + numberOfBlocks
136+ then Just blockNumber
137+ else Nothing
138+
139+ data TestnetWaitPeriod
140+ = WaitForEpochs EpochInterval
141+ | WaitForBlocks Word64
142+ | WaitForSlots Word64
143+ deriving Eq
144+
145+ instance Show TestnetWaitPeriod where
146+ show = \ case
147+ WaitForEpochs (EpochInterval n) -> " WaitForEpochs " <> show n
148+ WaitForBlocks n -> " WaitForBlocks " <> show n
149+ WaitForSlots n -> " WaitForSlots " <> show n
150+
151+ -- | Retries the action until it returns 'Just' or the timeout is reached
152+ retryUntilJustM
153+ :: HasCallStack
154+ => MonadIO m
155+ => MonadTest m
156+ => MonadAssertion m
157+ => EpochStateView
158+ -> TestnetWaitPeriod -- ^ timeout for an operation
159+ -> m (Maybe a )
160+ -> m a
161+ retryUntilJustM esv timeout act = withFrozenCallStack $ do
162+ startingValue <- getCurrentValue
163+ go startingValue
164+ where
165+ go startingValue = withFrozenCallStack $ do
166+ cv <- getCurrentValue
167+ when (timeoutW64 + startingValue < cv) $ do
168+ H. note_ $ " Action did not result in 'Just' - waited for: " <> show timeout
169+ H. failure
170+ act >>= \ case
171+ Just a -> pure a
172+ Nothing -> do
173+ H. threadDelay 300_000
174+ go startingValue
175+
176+ getCurrentValue = withFrozenCallStack $
177+ case timeout of
178+ WaitForEpochs _ -> unEpochNo <$> getCurrentEpochNo esv
179+ WaitForSlots _ -> unSlotNo <$> getSlotNumber esv
180+ WaitForBlocks _ -> unBlockNo <$> getBlockNumber esv
181+
182+ timeoutW64 =
183+ case timeout of
184+ WaitForEpochs (EpochInterval n) -> fromIntegral n
185+ WaitForSlots n -> n
186+ WaitForBlocks n -> n
187+
107188-- | A read-only mutable pointer to an epoch state, updated automatically
108189data EpochStateView = EpochStateView
109190 { nodeConfigPath :: ! (NodeConfigFile In )
191+ -- ^ node configuration file path
110192 , socketPath :: ! SocketPath
111- , epochStateView :: ! (IORef (Maybe AnyNewEpochState ))
193+ -- ^ node socket path, to which foldEpochState is connected to
194+ , epochStateView :: ! (IORef (Maybe (AnyNewEpochState , SlotNo , BlockNo )))
195+ -- ^ Automatically updated current NewEpochState. Use 'getEpochState', 'getBlockNumber', 'getSlotNumber'
196+ -- to access the values.
112197 }
113198
114199-- | Get epoch state from the view. If the state isn't available, retry waiting up to 15 seconds. Fails when
115200-- the state is not available after 15 seconds.
116- getEpochState :: MonadTest m
117- => MonadAssertion m
118- => MonadIO m
119- => EpochStateView
120- -> m AnyNewEpochState
121- getEpochState EpochStateView {epochStateView} =
201+ getEpochState
202+ :: HasCallStack
203+ => MonadTest m
204+ => MonadAssertion m
205+ => MonadIO m
206+ => EpochStateView
207+ -> m AnyNewEpochState
208+ getEpochState epochStateView =
209+ withFrozenCallStack $ getEpochStateDetails epochStateView $ \ (nes, _, _) -> pure nes
210+
211+ getBlockNumber
212+ :: HasCallStack
213+ => MonadIO m
214+ => MonadTest m
215+ => MonadAssertion m
216+ => EpochStateView
217+ -> m BlockNo -- ^ The number of last produced block
218+ getBlockNumber epochStateView =
219+ withFrozenCallStack $ getEpochStateDetails epochStateView $ \ (_, _, blockNumber) -> pure blockNumber
220+
221+ getSlotNumber
222+ :: HasCallStack
223+ => MonadIO m
224+ => MonadTest m
225+ => MonadAssertion m
226+ => EpochStateView
227+ -> m SlotNo -- ^ The current slot number
228+ getSlotNumber epochStateView =
229+ withFrozenCallStack $ getEpochStateDetails epochStateView $ \ (_, slotNumber, _) -> pure slotNumber
230+
231+ -- | Utility function for accessing epoch state in `IORef`
232+ getEpochStateDetails
233+ :: HasCallStack
234+ => MonadAssertion m
235+ => MonadTest m
236+ => MonadIO m
237+ => EpochStateView
238+ -> ((AnyNewEpochState , SlotNo , BlockNo ) -> m a )
239+ -> m a
240+ getEpochStateDetails EpochStateView {epochStateView} f =
122241 withFrozenCallStack $
123242 H. byDurationM 0.5 15 " EpochStateView has not been initialized within 15 seconds" $
124- H. evalIO (readIORef epochStateView) >>= maybe H. failure pure
125-
243+ H. evalIO (readIORef epochStateView) >>= maybe H. failure f
126244
127245-- | Create a background thread listening for new epoch states. New epoch states are available to access
128246-- through 'EpochStateView', using query functions.
@@ -137,11 +255,38 @@ getEpochStateView
137255getEpochStateView nodeConfigFile socketPath = withFrozenCallStack $ do
138256 epochStateView <- H. evalIO $ newIORef Nothing
139257 runInBackground . runExceptT . foldEpochState nodeConfigFile socketPath QuickValidation (EpochNo maxBound ) Nothing
140- $ \ epochState _slotNb _blockNb -> do
141- liftIO $ writeIORef epochStateView ( Just epochState)
258+ $ \ epochState slotNumber blockNumber -> do
259+ liftIO . writeIORef epochStateView $ Just ( epochState, slotNumber, blockNumber )
142260 pure ConditionNotMet
143261 pure $ EpochStateView nodeConfigFile socketPath epochStateView
144262
263+ -- | Watch the epoch state view until the guard function returns 'Just' or the timeout epoch is reached.
264+ -- Executes the guard function every 300ms. Waits for at most @maxWait@ epochs.
265+ -- The function will return the result of the guard function if it is met within the number of epochs,
266+ -- otherwise it will return @Nothing@.
267+ watchEpochStateUpdate
268+ :: forall m a . (HasCallStack , MonadIO m , MonadTest m , MonadAssertion m )
269+ => EpochStateView -- ^ The info to access the epoch state
270+ -> EpochInterval -- ^ The maximum number of epochs to wait
271+ -> ((AnyNewEpochState , SlotNo , BlockNo ) -> m (Maybe a )) -- ^ The guard function (@Just@ if the condition is met, @Nothing@ otherwise)
272+ -> m (Maybe a )
273+ watchEpochStateUpdate epochStateView (EpochInterval maxWait) f = withFrozenCallStack $ do
274+ AnyNewEpochState _ newEpochState <- getEpochState epochStateView
275+ let EpochNo currentEpoch = L. nesEL newEpochState
276+ go $ currentEpoch + fromIntegral maxWait
277+ where
278+ go :: Word64 -> m (Maybe a )
279+ go timeout = do
280+ newEpochStateDetails@ (AnyNewEpochState _ newEpochState', _, _) <- getEpochStateDetails epochStateView pure
281+ let EpochNo currentEpoch = L. nesEL newEpochState'
282+ f newEpochStateDetails >>= \ case
283+ Just result -> pure (Just result)
284+ Nothing
285+ | currentEpoch > timeout -> pure Nothing
286+ | otherwise -> do
287+ H. threadDelay 300_000
288+ go timeout
289+
145290-- | Retrieve all UTxOs map from the epoch state view.
146291findAllUtxos
147292 :: forall era m . HasCallStack
@@ -210,7 +355,7 @@ findLargestUtxoWithAddress epochStateView sbe address = withFrozenCallStack $ do
210355 $ sortOn (\ (_, TxOut _ txOutValue _ _) -> Down $ txOutValueToLovelace txOutValue) utxos
211356
212357-- | Retrieve a largest UTxO for a payment key info - a convenience wrapper for
213- -- 'findLargestUtxoForPaymentKey '.
358+ -- 'findLargestUtxoWithAddress '.
214359findLargestUtxoForPaymentKey
215360 :: MonadTest m
216361 => MonadAssertion m
@@ -268,7 +413,7 @@ checkDRepState epochStateView@EpochStateView{nodeConfigPath, socketPath} sbe f =
268413 currentEpoch <- getCurrentEpochNo epochStateView
269414 let terminationEpoch = succ . succ $ currentEpoch
270415 result <- H. evalIO . runExceptT $ foldEpochState nodeConfigPath socketPath QuickValidation terminationEpoch Nothing
271- $ \ (AnyNewEpochState actualEra newEpochState) _slotNb _blockNb -> do
416+ $ \ (AnyNewEpochState actualEra newEpochState) _slotNumber _blockNumber -> do
272417 Refl <- either error pure $ assertErasEqual sbe actualEra
273418 let dreps = shelleyBasedEraConstraints sbe newEpochState
274419 ^. L. nesEsL
@@ -364,65 +509,45 @@ getCurrentEpochNo epochStateView = withFrozenCallStack $ do
364509-- or it becomes the same within the @maxWait@ epochs. If the value is not reached within the time frame,
365510-- the test fails.
366511assertNewEpochState
367- :: forall m era value .
368- (Show value , MonadAssertion m , MonadTest m , MonadIO m , Eq value , HasCallStack )
512+ :: forall m era value . HasCallStack
513+ => Show value
514+ => Eq value
515+ => MonadAssertion m
516+ => MonadTest m
517+ => MonadIO m
369518 => EpochStateView -- ^ Current epoch state view. It can be obtained using the 'getEpochStateView' function.
370- -> ConwayEraOnwards era -- ^ The ConwayEraOnwards witness for current era.
371- -> value -- ^ The expected value to check in the epoch state.
519+ -> ShelleyBasedEra era -- ^ The ShelleyBasedEra witness for current era.
372520 -> EpochInterval -- ^ The maximum wait time in epochs.
373- -> Lens' (L. NewEpochState (ShelleyLedgerEra era )) value -- ^ The lens to access the specific value in the epoch state.
521+ -> Lens' (L. NewEpochState (ShelleyLedgerEra era )) value
522+ -- ^ The lens to access the specific value in the epoch state.
523+ -> value -- ^ The expected value to check in the epoch state.
374524 -> m ()
375- assertNewEpochState epochStateView ceo expected maxWait lens = withFrozenCallStack $ do
376- let sbe = conwayEraOnwardsToShelleyBasedEra ceo
377- mStateView <- watchEpochStateView epochStateView (checkEpochState sbe) maxWait
378- case mStateView of
379- Just () -> pure ()
380- Nothing -> do epochState <- getEpochState epochStateView
381- val <- getFromEpochState sbe epochState
382- if val == expected
383- then pure ()
384- else H. failMessage callStack $ unlines
385- [ " assertNewEpochState: expected value not reached within the time frame."
386- , " Expected value: " <> show expected
387- , " Actual value: " <> show val
388- ]
525+ assertNewEpochState epochStateView sbe maxWait lens expected = withFrozenCallStack $ do
526+ mStateView <- watchEpochStateUpdate epochStateView maxWait (const checkEpochState)
527+ when (isNothing mStateView) $ do
528+ val <- getFromEpochStateForEra
529+ -- there's a tiny tiny chance that the value has changed since 'watchEpochStateUpdate'
530+ -- so check it again
531+ if val == expected
532+ then pure ()
533+ else H. failMessage callStack $ unlines
534+ [ " assertNewEpochState: expected value not reached within the time frame."
535+ , " Expected value: " <> show expected
536+ , " Actual value: " <> show val
537+ ]
389538 where
390- checkEpochState :: HasCallStack
391- => ShelleyBasedEra era -> AnyNewEpochState -> m (Maybe () )
392- checkEpochState sbe newEpochState = do
393- val <- getFromEpochState sbe newEpochState
394- return $ if val == expected then Just () else Nothing
395-
396- getFromEpochState :: HasCallStack
397- => ShelleyBasedEra era -> AnyNewEpochState -> m value
398- getFromEpochState sbe (AnyNewEpochState actualEra newEpochState) = do
399- Refl <- either error pure $ assertErasEqual sbe actualEra
400- return $ newEpochState ^. lens
539+ checkEpochState
540+ :: HasCallStack
541+ => m (Maybe () )
542+ checkEpochState = withFrozenCallStack $ do
543+ val <- getFromEpochStateForEra
544+ pure $ if val == expected then Just () else Nothing
545+
546+ getFromEpochStateForEra
547+ :: HasCallStack
548+ => m value
549+ getFromEpochStateForEra = withFrozenCallStack $ getEpochStateDetails epochStateView $
550+ \ (AnyNewEpochState actualEra newEpochState, _, _) -> do
551+ Refl <- H. leftFail $ assertErasEqual sbe actualEra
552+ pure $ newEpochState ^. lens
401553
402- -- | Watch the epoch state view until the guard function returns 'Just' or the timeout epoch is reached.
403- -- Wait for at most @maxWait@ epochs.
404- -- The function will return the result of the guard function if it is met, otherwise it will return @Nothing@.
405- watchEpochStateView
406- :: forall m a . (HasCallStack , MonadIO m , MonadTest m , MonadAssertion m )
407- => EpochStateView -- ^ The info to access the epoch state
408- -> (AnyNewEpochState -> m (Maybe a )) -- ^ The guard function (@Just@ if the condition is met, @Nothing@ otherwise)
409- -> EpochInterval -- ^ The maximum number of epochs to wait
410- -> m (Maybe a )
411- watchEpochStateView epochStateView f (EpochInterval maxWait) = withFrozenCallStack $ do
412- AnyNewEpochState _ newEpochState <- getEpochState epochStateView
413- let EpochNo currentEpoch = L. nesEL newEpochState
414- go (EpochNo $ currentEpoch + fromIntegral maxWait)
415- where
416- go :: EpochNo -> m (Maybe a )
417- go (EpochNo timeout) = do
418- epochState@ (AnyNewEpochState _ newEpochState') <- getEpochState epochStateView
419- let EpochNo currentEpoch = L. nesEL newEpochState'
420- condition <- f epochState
421- case condition of
422- Just result -> pure (Just result)
423- Nothing -> do
424- if currentEpoch > timeout
425- then pure Nothing
426- else do
427- H. threadDelay 10_000
428- go (EpochNo timeout)
0 commit comments