Skip to content

Commit ce13b8b

Browse files
committed
consensus-test: improve legibility in the ChainSync client test driver
Main change: remove the LogicalClock, since it's quite a bit of indirection with no real gain. Also, remove -XRecordWildCards.
1 parent 6614998 commit ce13b8b

File tree

1 file changed

+145
-98
lines changed
  • ouroboros-consensus/test/consensus-test/Test/Consensus/MiniProtocol/ChainSync

1 file changed

+145
-98
lines changed

ouroboros-consensus/test/consensus-test/Test/Consensus/MiniProtocol/ChainSync/Client.hs

Lines changed: 145 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
{-# LANGUAGE FlexibleContexts #-}
33
{-# LANGUAGE GADTs #-}
44
{-# LANGUAGE LambdaCase #-}
5+
{-# LANGUAGE NamedFieldPuns #-}
56
{-# LANGUAGE RankNTypes #-}
6-
{-# LANGUAGE RecordWildCards #-}
77
{-# LANGUAGE ScopedTypeVariables #-}
88
{-# LANGUAGE TypeApplications #-}
99
-- | Tests for the chain sync client.
@@ -28,8 +28,7 @@ import Cardano.Crypto.DSIGN.Mock
2828
import Control.Monad (unless, void, when)
2929
import Control.Monad.Class.MonadThrow (Handler (..), catches)
3030
import Control.Monad.IOSim (runSimOrThrow)
31-
import Control.Tracer (contramap, nullTracer)
32-
import Data.Bifunctor (first)
31+
import Control.Tracer (contramap, contramapM, nullTracer)
3332
import Data.List (intercalate)
3433
import qualified Data.Map.Strict as Map
3534
import Data.Maybe (isJust)
@@ -47,6 +46,7 @@ import qualified Ouroboros.Consensus.HeaderStateHistory as HeaderStateHistory
4746
import Ouroboros.Consensus.Ledger.Abstract
4847
import Ouroboros.Consensus.Ledger.Extended hiding (ledgerState)
4948
import Ouroboros.Consensus.MiniProtocol.ChainSync.Client
49+
import Ouroboros.Consensus.Node.NetworkProtocolVersion (NodeToNodeVersion)
5050
import Ouroboros.Consensus.Node.ProtocolInfo
5151
import Ouroboros.Consensus.NodeId
5252
import Ouroboros.Consensus.Protocol.BFT
@@ -57,7 +57,7 @@ import Ouroboros.Consensus.Util.Condense
5757
import Ouroboros.Consensus.Util.IOLike
5858
import Ouroboros.Consensus.Util.ResourceRegistry
5959
import Ouroboros.Consensus.Util.STM (Fingerprint (..),
60-
WithFingerprint (..), forkLinkedWatcher)
60+
WithFingerprint (..))
6161
import Ouroboros.Network.AnchoredFragment (AnchoredFragment)
6262
import qualified Ouroboros.Network.AnchoredFragment as AF
6363
import Ouroboros.Network.Block (getTipPoint)
@@ -79,8 +79,7 @@ import Test.Tasty
7979
import Test.Tasty.QuickCheck
8080
import Test.Util.ChainUpdates (ChainUpdate (..), UpdateBehavior (..),
8181
genChainUpdates, toChainUpdates)
82-
import qualified Test.Util.LogicalClock as LogicalClock
83-
import Test.Util.LogicalClock (NumTicks (..), Tick (..))
82+
import Test.Util.LogicalClock (Tick (..))
8483
import Test.Util.Orphans.Arbitrary ()
8584
import Test.Util.Orphans.IOLike ()
8685
import Test.Util.Schedule (Schedule (..), genSchedule, joinSchedule,
@@ -103,7 +102,13 @@ tests = testGroup "ChainSyncClient"
103102
-------------------------------------------------------------------------------}
104103

105104
prop_chainSync :: ChainSyncClientSetup -> Property
106-
prop_chainSync ChainSyncClientSetup {..} =
105+
prop_chainSync ChainSyncClientSetup {
106+
securityParam
107+
, clientUpdates
108+
, serverUpdates
109+
, startTick
110+
, invalidBlocks
111+
} =
107112
counterexample
108113
("Client chain: " <> ppChain finalClientChain <> "\n" <>
109114
"Server chain: " <> ppChain finalServerChain <> "\n" <>
@@ -140,8 +145,19 @@ prop_chainSync ChainSyncClientSetup {..} =
140145
where
141146
k = maxRollbacks securityParam
142147

143-
ChainSyncOutcome {..} = runSimOrThrow $
144-
runChainSync securityParam clientUpdates serverUpdates invalidBlocks startTick
148+
ChainSyncOutcome {
149+
finalClientChain
150+
, finalServerChain
151+
, mbResult
152+
, syncedFragment
153+
, traceEvents
154+
} = runSimOrThrow $
155+
runChainSync
156+
securityParam
157+
clientUpdates
158+
serverUpdates
159+
invalidBlocks
160+
startTick
145161

146162
clientFragment = AF.anchorNewest k $ Chain.toAnchoredFragment finalClientChain
147163

@@ -185,6 +201,10 @@ isSuffixOf fragment chain =
185201
serverId :: CoreNodeId
186202
serverId = CoreNodeId 1
187203

204+
-- | The schedule that determines the evolution of the local chain.
205+
--
206+
-- Note that the 'TestBlock' used in this test is constructed in such a way
207+
-- that the block's slot number equals its block number.
188208
newtype ClientUpdates =
189209
ClientUpdates { getClientUpdates :: Schedule ChainUpdate }
190210
deriving (Show)
@@ -248,7 +268,7 @@ runChainSync securityParam (ClientUpdates clientUpdates)
248268
(ServerUpdates serverUpdates) (InvalidBlocks invalidBlocks)
249269
startSyncingAt = withRegistry $ \registry -> do
250270

251-
clock <- LogicalClock.new registry numTicks
271+
varCurrentLogicalTick <- uncheckedNewTVarM (Tick 0)
252272

253273
-- Set up the client
254274
varCandidates <- uncheckedNewTVarM Map.empty
@@ -261,7 +281,12 @@ runChainSync securityParam (ClientUpdates clientUpdates)
261281
-- at the final state of each candidate.
262282
varFinalCandidates <- uncheckedNewTVarM Map.empty
263283

264-
(tracer, getTrace) <- first (LogicalClock.tickTracer clock) <$> recordingTracerTVar
284+
(tracer, getTrace) <- do
285+
(tracer', getTrace) <- recordingTracerTVar
286+
let foo ev = do
287+
now <- readTVarIO varCurrentLogicalTick
288+
pure (now, ev)
289+
pure (contramapM foo tracer', getTrace)
265290
let chainSyncTracer = contramap Left tracer
266291
protocolTracer = contramap Right tracer
267292

@@ -302,7 +327,7 @@ runChainSync securityParam (ClientUpdates clientUpdates)
302327
chainSyncTracer
303328
nodeCfg
304329
chainDbView
305-
maxBound
330+
(maxBound :: NodeToNodeVersion)
306331
(return Continue)
307332
nullTracer
308333

@@ -312,99 +337,98 @@ runChainSync securityParam (ClientUpdates clientUpdates)
312337
(Tip TestBlock) m ()
313338
server = chainSyncServerExample () varChainProducerState getHeader
314339

315-
-- Schedule updates of the client and server chains
316-
varLastUpdate <- uncheckedNewTVarM 0
317-
let forkLinkedTickWatcher :: (Tick -> m ()) -> m ()
318-
forkLinkedTickWatcher =
319-
void
320-
. forkLinkedWatcher registry "scheduled updates"
321-
. LogicalClock.tickWatcher clock
322-
forkLinkedTickWatcher $ \tick -> do
323-
-- Stop updating the client and server chains when the chain sync client
324-
-- has thrown an exception or has gracefully terminated, so that at the
325-
-- end, we can read the chains in the states they were in when the
326-
-- exception was thrown.
327-
stop <- fmap isJust $ atomically $ readTVar varClientResult
328-
unless stop $ do
329-
-- Newly discovered invalid blocks
330-
whenJust (Map.lookup tick (getSchedule invalidBlocks)) $
331-
atomically . modifyTVar varKnownInvalid . Set.union . Set.fromList
332-
333-
-- Client
334-
whenJust (Map.lookup tick (getSchedule clientUpdates)) $ \chainUpdates ->
335-
atomically $ modifyTVar varClientState $ updateClientState chainUpdates
336-
337-
-- Server
338-
whenJust (Map.lookup tick (getSchedule serverUpdates)) $ \chainUpdates ->
339-
atomically $ do
340-
chainProducerState <- readTVar varChainProducerState
341-
case CPS.applyChainUpdates
342-
(toChainUpdates chainUpdates)
343-
chainProducerState of
344-
Just chainProducerState' ->
345-
writeTVar varChainProducerState chainProducerState'
346-
Nothing ->
347-
error $ "Invalid chainUpdates: " <> show chainUpdates <>
348-
" for " <> show (chainState chainProducerState)
349-
atomically $ writeTVar varLastUpdate tick
340+
-- Do scheduled updates of the client and server chains
341+
let updateChainsDuringTick :: Tick -> m ()
342+
updateChainsDuringTick tick = do
343+
-- Stop updating the client and server chains when the chain sync client
344+
-- has thrown an exception or has gracefully terminated, so that at the
345+
-- end, we can read the chains in the states they were in when the
346+
-- exception was thrown.
347+
stop <- fmap isJust $ atomically $ readTVar varClientResult
348+
unless stop $ do
349+
-- Newly discovered invalid blocks
350+
whenJust (Map.lookup tick (getSchedule invalidBlocks)) $
351+
atomically . modifyTVar varKnownInvalid . Set.union . Set.fromList
352+
353+
-- Client
354+
doTick clientUpdates tick $ \chainUpdates ->
355+
atomically $ modifyTVar varClientState $ updateClientState chainUpdates
356+
357+
-- Server
358+
doTick serverUpdates tick $ \chainUpdates ->
359+
atomically $ do
360+
chainProducerState <- readTVar varChainProducerState
361+
case CPS.applyChainUpdates
362+
(toChainUpdates chainUpdates)
363+
chainProducerState of
364+
Just chainProducerState' ->
365+
writeTVar varChainProducerState chainProducerState'
366+
Nothing ->
367+
error $ "Invalid chainUpdates: " <> show chainUpdates <>
368+
" for " <> show (chainState chainProducerState)
350369

351370
-- Connect client to server and run the chain sync protocol
352-
LogicalClock.onTick registry clock "startSyncing" startSyncingAt $ do
353-
-- When updates are planned at the same time that we start syncing, we
354-
-- wait until these updates are done before we start syncing.
355-
when (Map.member startSyncingAt (getSchedule clientUpdates) ||
356-
Map.member startSyncingAt (getSchedule serverUpdates)) $
357-
atomically $ do
358-
lastUpdate <- readTVar varLastUpdate
359-
check (lastUpdate == startSyncingAt)
360-
361-
(clientChannel, serverChannel) <- createConnectedChannels
362-
-- Don't link the thread (which will cause the exception to be rethrown
363-
-- in the main thread), just catch the exception and store it, because
364-
-- we want a "regular ending".
365-
void $ forkThread registry "ChainSyncClient" $
366-
bracketChainSyncClient
367-
chainSyncTracer
368-
chainDbView
369-
varCandidates
370-
serverId
371-
maxBound $ \varCandidate -> do
372-
atomically $ modifyTVar varFinalCandidates $
373-
Map.insert serverId varCandidate
374-
result <-
375-
runPipelinedPeer protocolTracer codecChainSyncId clientChannel $
376-
chainSyncClientPeerPipelined $ client varCandidate
377-
atomically $ writeTVar varClientResult (Just (Right result))
378-
return ()
379-
`catchAlsoLinked` \ex -> do
380-
atomically $ writeTVar varClientResult (Just (Left ex))
381-
-- Rethrow, but it will be ignored anyway.
382-
throwIO ex
383-
void $ forkLinkedThread registry "ChainSyncServer" $
384-
runPeer nullTracer codecChainSyncId serverChannel
385-
(chainSyncServerPeer server)
386-
387-
LogicalClock.waitUntilDone clock
388-
-- Wait a random amount of time after the final tick for the chain sync
389-
-- to finish
390-
threadDelay 2000
371+
--
372+
-- Happens /immediately after/ the chain and clock effects schedule for
373+
-- 'startSyncingAt'.
374+
let initiateChainSync = do
375+
(clientChannel, serverChannel) <- createConnectedChannels
376+
-- Don't link the thread (which will cause the exception to be
377+
-- rethrown in the main thread), just catch the exception and store
378+
-- it, because we want a "regular ending".
379+
void $ forkThread registry "ChainSyncClient" $
380+
bracketChainSyncClient
381+
chainSyncTracer
382+
chainDbView
383+
varCandidates
384+
serverId
385+
maxBound $ \varCandidate -> do
386+
atomically $ modifyTVar varFinalCandidates $
387+
Map.insert serverId varCandidate
388+
result <-
389+
runPipelinedPeer protocolTracer codecChainSyncId clientChannel $
390+
chainSyncClientPeerPipelined $ client varCandidate
391+
atomically $ writeTVar varClientResult (Just (Right result))
392+
return ()
393+
`catchAlsoLinked` \ex -> do
394+
atomically $ writeTVar varClientResult (Just (Left ex))
395+
-- Rethrow, but it will be ignored anyway.
396+
throwIO ex
397+
void $ forkLinkedThread registry "ChainSyncServer" $
398+
runPeer nullTracer codecChainSyncId serverChannel
399+
(chainSyncServerPeer server)
400+
401+
do
402+
let loop tick = do
403+
updateChainsDuringTick tick
404+
when (tick == startSyncingAt) $ initiateChainSync
405+
when (tick < finalTick) $ loop (tick + 1)
406+
loop (Tick 1)
407+
408+
-- This delay seems enough to let all threads finish their final work.
409+
--
410+
-- TODO what is the necessary threshold?
411+
threadDelay 86400
391412

392413
traceEvents <- getTrace
393414
-- Collect the return values
394415
atomically $ do
395-
finalClientChain <- readTVar varClientState
396-
finalServerChain <- chainState <$> readTVar varChainProducerState
416+
finalClientChain <- readTVar varClientState
417+
finalServerChain <- chainState <$> readTVar varChainProducerState
397418
candidateFragment <- readTVar varFinalCandidates >>= readTVar . (Map.! serverId)
398-
mbResult <- readTVar varClientResult
419+
mbResult <- readTVar varClientResult
399420
return ChainSyncOutcome {
400-
syncedFragment = AF.mapAnchoredFragment testHeader candidateFragment
401-
, ..
421+
finalClientChain
422+
, finalServerChain
423+
, mbResult
424+
, syncedFragment = AF.mapAnchoredFragment testHeader candidateFragment
425+
, traceEvents
402426
}
403427
where
404428
k = maxRollbacks securityParam
405429

406-
slotLength :: SlotLength
407-
slotLength = slotLengthFromSec 20
430+
doTick :: Schedule a -> Tick -> ([a] -> m ()) -> m ()
431+
doTick sched tick kont = whenJust (Map.lookup tick (getSchedule sched)) kont
408432

409433
nodeCfg :: TopLevelConfig TestBlock
410434
nodeCfg = TopLevelConfig {
@@ -431,8 +455,8 @@ runChainSync securityParam (ClientUpdates clientUpdates)
431455
numCoreNodes :: NumCoreNodes
432456
numCoreNodes = NumCoreNodes 2
433457

434-
numTicks :: NumTicks
435-
numTicks = LogicalClock.sufficientTimeFor
458+
finalTick :: Tick
459+
finalTick = maximum
436460
[ lastTick clientUpdates
437461
, lastTick serverUpdates
438462
, startSyncingAt
@@ -502,6 +526,12 @@ computeHeaderStateHistory cfg =
502526
ChainSyncClientSetup
503527
-------------------------------------------------------------------------------}
504528

529+
slotLength :: SlotLength
530+
slotLength = slotLengthFromSec $ toEnum slotLengthInSeconds
531+
532+
slotLengthInSeconds :: Int
533+
slotLengthInSeconds = 20
534+
505535
-- | Bundle dependent arguments for test generation
506536
data ChainSyncClientSetup = ChainSyncClientSetup
507537
{ securityParam :: SecurityParam
@@ -535,8 +565,19 @@ instance Arbitrary ChainSyncClientSetup where
535565
, tbValid b == Invalid
536566
]
537567
invalidBlocks <- InvalidBlocks <$> (genSchedule =<< shuffle trapBlocks)
538-
return ChainSyncClientSetup {..}
539-
shrink cscs@ChainSyncClientSetup {..} =
568+
569+
return ChainSyncClientSetup {
570+
securityParam
571+
, clientUpdates
572+
, serverUpdates
573+
, startTick
574+
, invalidBlocks
575+
}
576+
shrink cscs@ChainSyncClientSetup {
577+
clientUpdates
578+
, serverUpdates
579+
, startTick
580+
} =
540581
-- We don't shrink 'securityParam' because the updates depend on it
541582

542583
-- We also don't shrink 'invalidBlocks' right now (as it does not impact
@@ -573,7 +614,13 @@ instance Arbitrary ChainSyncClientSetup where
573614
]
574615

575616
instance Show ChainSyncClientSetup where
576-
show ChainSyncClientSetup {..} = unlines
617+
show ChainSyncClientSetup {
618+
securityParam
619+
, clientUpdates
620+
, serverUpdates
621+
, startTick
622+
, invalidBlocks
623+
} = unlines
577624
[ "ChainSyncClientSetup:"
578625
, "securityParam: " <> show (maxRollbacks securityParam)
579626
, "clientUpdates:"

0 commit comments

Comments
 (0)