Skip to content

Commit 1c4b97e

Browse files
committedJan 26, 2025
simulation: handle NumCores in praosNode
1 parent 003a716 commit 1c4b97e

11 files changed

+214
-265
lines changed
 

‎simulation/ouroboros-leios-sim.cabal

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ library
6161
LeiosProtocol.Short.VizSim
6262
LeiosProtocol.Short.VizSimP2P
6363
LeiosProtocol.SimTestRelay
64-
LeiosProtocol.TaskMultiQueue
6564
LeiosProtocol.VizSimTestRelay
6665
ModelTCP
6766
JSONCompat
@@ -91,6 +90,7 @@ library
9190
STMCompat
9291
SimTCPLinks
9392
SimTypes
93+
TaskMultiQueue
9494
TimeCompat
9595
Topology
9696
Viz

‎simulation/src/LeiosProtocol/Short/Generate.hs

+14-49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# LANGUAGE BangPatterns #-}
22
{-# LANGUAGE GADTs #-}
33
{-# LANGUAGE KindSignatures #-}
4+
{-# LANGUAGE NamedFieldPuns #-}
45
{-# LANGUAGE OverloadedRecordDot #-}
56
{-# LANGUAGE OverloadedStrings #-}
67
{-# LANGUAGE RecordWildCards #-}
@@ -14,16 +15,13 @@ import Control.Monad (forM)
1415
import Control.Monad.State (
1516
MonadState (get, put),
1617
MonadTrans (lift),
17-
StateT (runStateT),
18-
gets,
19-
runState,
18+
StateT,
2019
)
2120
import Data.Bifunctor (Bifunctor (..))
2221
import Data.Kind (Type)
2322
import LeiosProtocol.Common
2423
import LeiosProtocol.Short hiding (Stage (..))
2524
import STMCompat
26-
import System.Random (StdGen, uniformR)
2725

2826
--------------------------------------------------------------------------------
2927

@@ -50,42 +48,7 @@ data SomeRole :: Type where
5048
data SomeAction :: Type where
5149
SomeAction :: Role a -> a -> SomeAction
5250

53-
mkScheduler :: MonadSTM m => StdGen -> (SlotNo -> [(a, Maybe (Double -> Word64))]) -> m (SlotNo -> m [(a, Word64)])
54-
mkScheduler rng0 rates = do
55-
let
56-
sampleRates (_role, Nothing) = return []
57-
sampleRates (role, Just f) = do
58-
(sample, rng') <- gets $ uniformR (0, 1)
59-
put $! rng'
60-
let wins = f sample
61-
return [(role, wins) | wins >= 1]
62-
rngVar <- newTVarIO rng0
63-
let sched slot = atomically $ do
64-
rng <- readTVar rngVar
65-
let (acts, rng1) = flip runState rng . fmap concat . mapM sampleRates $ rates slot
66-
writeTVar rngVar rng1
67-
return acts
68-
return sched
69-
70-
-- | @waitNextSlot cfg targetSlot@ waits until the beginning of
71-
-- @targetSlot@ if that's now or in the future, otherwise the closest slot.
72-
waitNextSlot :: (Monad m, MonadTime m, MonadDelay m) => SlotConfig -> SlotNo -> m SlotNo
73-
waitNextSlot slotConfig targetSlot = do
74-
now <- getCurrentTime
75-
let targetSlotTime = slotTime slotConfig targetSlot
76-
let slot
77-
| now <= targetSlotTime = targetSlot
78-
| otherwise = assert (nextSlotIndex >= 0) $ toEnum nextSlotIndex
79-
where
80-
nextSlotIndex =
81-
assert (slotConfig.duration == 1) $
82-
ceiling $
83-
now `diffUTCTime` slotConfig.start
84-
let tgt = slotTime slotConfig slot
85-
threadDelayNDT (tgt `diffUTCTime` now)
86-
return slot
87-
88-
data BlockGeneratorConfig m = BlockGeneratorConfig
51+
data LeiosGeneratorConfig m = LeiosGeneratorConfig
8952
{ leios :: LeiosConfig
9053
, slotConfig :: SlotConfig
9154
, nodeId :: NodeId
@@ -94,19 +57,21 @@ data BlockGeneratorConfig m = BlockGeneratorConfig
9457
, submit :: [(DiffTime, SomeAction)] -> m ()
9558
}
9659

97-
blockGenerator ::
60+
leiosBlockGenerator ::
9861
forall m.
9962
(MonadSTM m, MonadDelay m, MonadTime m) =>
100-
BlockGeneratorConfig m ->
63+
LeiosGeneratorConfig m ->
10164
m ()
102-
blockGenerator BlockGeneratorConfig{..} = go (0, 0)
65+
leiosBlockGenerator LeiosGeneratorConfig{..} =
66+
blockGenerator $
67+
BlockGeneratorConfig
68+
{ execute = \slot -> do
69+
roles <- lift $ schedule slot
70+
actions <- concat <$> mapM (execute slot) roles
71+
lift $ submit actions
72+
, slotConfig
73+
}
10374
where
104-
go (!blkId, !tgtSlot) = do
105-
slot <- waitNextSlot slotConfig tgtSlot
106-
roles <- schedule slot
107-
(actions, blkId') <- runStateT (concat <$> mapM (execute slot) roles) blkId
108-
submit actions
109-
go (blkId', slot + 1)
11075
execute slot (SomeRole r, wins) = assert (wins >= 1) $ (map . second) (SomeAction r) <$> execute' slot r wins
11176
execute' :: SlotNo -> Role a -> Word64 -> StateT Int m [(DiffTime, a)]
11277
execute' slot Base _wins = do

‎simulation/src/LeiosProtocol/Short/Node.hs

+6-26
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import Control.Monad.Class.MonadThrow
2424
import Control.Tracer
2525
import Data.Coerce (coerce)
2626
import Data.Foldable (forM_)
27-
import Data.Ix (Ix, range)
27+
import Data.Ix (Ix)
2828
import Data.List (sort, sortOn)
2929
import Data.Map (Map)
3030
import qualified Data.Map.Strict as Map
@@ -39,7 +39,6 @@ import qualified LeiosProtocol.RelayBuffer as RB
3939
import LeiosProtocol.Short
4040
import LeiosProtocol.Short.Generate
4141
import qualified LeiosProtocol.Short.Generate as Generate
42-
import LeiosProtocol.TaskMultiQueue
4342
import Numeric.Natural (Natural)
4443
import PraosProtocol.BlockFetch (
4544
BlockFetchControllerState (blocksVar),
@@ -51,7 +50,7 @@ import qualified PraosProtocol.PraosNode as PraosNode
5150
import STMCompat
5251
import SimTypes (cpuTask)
5352
import System.Random
54-
import WorkerPool
53+
import TaskMultiQueue
5554

5655
--------------------------------------------------------------
5756
---- Events
@@ -99,9 +98,9 @@ data LeiosNodeState m = LeiosNodeState
9998
, relayVoteState :: !(RelayVoteState m)
10099
, ibDeliveryTimesVar :: !(TVar m (Map InputBlockId UTCTime))
101100
, taskQueue :: !(TaskMultiQueue LeiosNodeTask m)
102-
, waitingForRBVar :: !(TVar m (Map (HeaderHash RankingBlock) [m ()]))
101+
, waitingForRBVar :: !(TVar m (Map (HeaderHash RankingBlock) [STM m ()]))
103102
-- ^ waiting for RB block itself to be validated.
104-
, waitingForLedgerStateVar :: !(TVar m (Map (HeaderHash RankingBlock) [m ()]))
103+
, waitingForLedgerStateVar :: !(TVar m (Map (HeaderHash RankingBlock) [STM m ()]))
105104
-- ^ waiting for ledger state of RB block to be validated.
106105
, ledgerStateVar :: !(TVar m (Map (HeaderHash RankingBlock) LedgerState))
107106
, ibsNeededForEBVar :: !(TVar m (Map EndorseBlockId (Set InputBlockId)))
@@ -400,25 +399,6 @@ leiosNode tracer cfg followers peers = do
400399
, pruningThreads
401400
]
402401

403-
processCPUTasks ::
404-
(MonadSTM m, MonadDelay m, MonadMonotonicTimeNSec m, MonadFork m, MonadAsync m, MonadCatch m) =>
405-
NumCores ->
406-
Tracer m CPUTask ->
407-
TaskMultiQueue LeiosNodeTask m ->
408-
m ()
409-
processCPUTasks Infinite tracer queue = forever $ runInfParallelBlocking tracer queue
410-
processCPUTasks (Finite n) tracer queue = newBoundedWorkerPool n [taskSource l | l <- range (minBound, maxBound)]
411-
where
412-
taskSource l = do
413-
(cpu, m) <- readTMQueue queue l
414-
var <- newEmptyTMVar
415-
let action = do
416-
traceWith tracer cpu
417-
threadDelay (cpuTaskDuration cpu)
418-
m
419-
-- TODO: read from var and log exception.
420-
return $ Task action var
421-
422402
computeLedgerStateThread ::
423403
forall m.
424404
(MonadMVar m, MonadFork m, MonadAsync m, MonadSTM m, MonadTime m, MonadDelay m) =>
@@ -456,7 +436,7 @@ dispatchValidation ::
456436
dispatchValidation tracer cfg leiosState req =
457437
atomically $ mapM_ (uncurry $ writeTMQueue leiosState.taskQueue) =<< go req
458438
where
459-
queue = atomically . mapM_ (uncurry $ writeTMQueue leiosState.taskQueue)
439+
queue = mapM_ (uncurry $ writeTMQueue leiosState.taskQueue)
460440
labelTask (tag, (f, m)) = let !task = f (show tag) in (tag, (task, m))
461441
valRB rb m = do
462442
let task prefix = cpuTask prefix cfg.leios.praos.blockValidationDelay rb
@@ -561,7 +541,7 @@ generator tracer cfg st = do
561541
atomically $ modifyTVar' st.relayVoteState.relayBufferVar (RB.snoc v.id (v.id, v))
562542
traceWith tracer (LeiosNodeEvent Generate (EventVote v))
563543
let LeiosNodeConfig{..} = cfg
564-
blockGenerator $ BlockGeneratorConfig{submit = mapM_ submitOne, ..}
544+
leiosBlockGenerator $ LeiosGeneratorConfig{submit = mapM_ submitOne, ..}
565545

566546
mkBuffersView :: forall m. MonadSTM m => LeiosNodeConfig -> LeiosNodeState m -> BuffersView m
567547
mkBuffersView cfg st = BuffersView{..}

‎simulation/src/PraosProtocol/BlockFetch.hs

+13-67
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ import Chan (Chan)
2626
import ChanDriver (ProtocolMessage, chanDriver)
2727
import Control.Exception (assert)
2828
import Control.Monad (forM, forever, guard, join, unless, void, when, (<=<))
29-
import Control.Tracer (Contravariant (contramap), Tracer, traceWith)
29+
import Control.Tracer (Tracer, traceWith)
3030
import Data.Bifunctor (second)
31-
import Data.Foldable (forM_)
3231
import Data.Kind (Type)
3332
import qualified Data.List as List
3433
import Data.Map.Strict (Map)
@@ -47,7 +46,6 @@ import Network.TypedProtocol (
4746
import Network.TypedProtocol.Driver (runPeerWithDriver)
4847
import qualified Network.TypedProtocol.Peer.Client as TC
4948
import qualified Network.TypedProtocol.Peer.Server as TS
50-
import Numeric.Natural (Natural)
5149
import PraosProtocol.Common
5250
import qualified PraosProtocol.Common.AnchoredFragment as AnchoredFragment
5351
import qualified PraosProtocol.Common.Chain as Chain
@@ -602,82 +600,30 @@ initBlockFetchConsumerStateForPeerId tracer peerId blockFetchControllerState sub
602600

603601
setupValidatorThreads ::
604602
(MonadSTM m, MonadDelay m) =>
605-
Tracer m (PraosNodeEvent BlockBody) ->
606603
PraosConfig BlockBody ->
607604
BlockFetchControllerState BlockBody m ->
608-
-- | bound on queue length.
609-
Natural ->
605+
((CPUTask, m ()) -> STM m ()) ->
610606
m ([m ()], Block BlockBody -> m () -> m ())
611-
setupValidatorThreads tracer cfg st n = do
612-
queue <- newTBQueueIO n
613-
(waitingVar, processWaitingThread) <- setupProcessWaitingThread (contramap PraosNodeEventCPU tracer) (Just 1) st.blocksVar
614-
let doTask (cpuTask, m) = do
615-
traceWith tracer . PraosNodeEventCPU $ cpuTask
616-
threadDelay cpuTask.cpuTaskDuration
617-
m
618-
619-
-- if we have the previous block, we process the task sequentially to provide back pressure on the queue.
620-
let waitForPrev block task = case blockPrevHash block of
621-
GenesisHash -> doTask task
607+
setupValidatorThreads cfg st queue = do
608+
waitingVar <- newTVarIO Map.empty
609+
let processWaitingThread = processWaiting' st.blocksVar waitingVar
610+
611+
let waitForPrev block task = atomically $ case blockPrevHash block of
612+
GenesisHash -> queue task
622613
BlockHash prev -> do
623-
havePrev <- Map.member prev <$> readTVarIO st.blocksVar
624-
-- Note: for pure praos this also means we have the ledger state.
625-
if havePrev
626-
then doTask task
627-
else atomically $ modifyTVar' waitingVar (Map.insertWith (++) prev [task])
628-
fetch = forever $ do
629-
(block, completion) <- atomically $ readTBQueue queue
614+
modifyTVar' waitingVar (Map.insertWith (++) prev [queue task])
615+
add block completion = do
630616
assert (blockInvariant block) $ do
631617
waitForPrev block $
632618
let !cpuTask = CPUTask (cfg.blockValidationDelay block) (T.pack $ "Validate " ++ show (blockHash block))
633619
in (cpuTask, completion)
634-
add block completion = atomically $ writeTBQueue queue (block, completion)
635-
return ([fetch, processWaitingThread], add)
636-
637-
setupProcessWaitingThread ::
638-
forall m a b.
639-
(MonadSTM m, MonadDelay m) =>
640-
Tracer m CPUTask ->
641-
-- | how many waiting to process in parallel
642-
Maybe Int ->
643-
TVar m (Map ConcreteHeaderHash a) ->
644-
m (TVar m (Map ConcreteHeaderHash [(CPUTask, m b)]), m ())
645-
setupProcessWaitingThread tracer npar blocksVar = do
646-
waitingVar <- newTVarIO Map.empty
647-
return (waitingVar, processWaiting tracer npar blocksVar waitingVar)
648-
649-
processWaiting ::
650-
forall m a b.
651-
(MonadSTM m, MonadDelay m) =>
652-
Tracer m CPUTask ->
653-
-- | how many waiting to process in parallel
654-
Maybe Int ->
655-
TVar m (Map ConcreteHeaderHash a) ->
656-
TVar m (Map ConcreteHeaderHash [(CPUTask, m b)]) ->
657-
m ()
658-
processWaiting tracer npar blocksVar waitingVar = go
659-
where
660-
parallelDelay xs = do
661-
let !d = maximum $ map (cpuTaskDuration . fst) xs
662-
forM_ xs $ traceWith tracer . fst
663-
threadDelay d
664-
mapM_ snd xs
665-
go = forever $ join $ atomically $ do
666-
waiting <- readTVar waitingVar
667-
when (Map.null waiting) retry
668-
blocks <- readTVar blocksVar
669-
let toValidate = Map.intersection waiting blocks
670-
when (Map.null toValidate) retry
671-
writeTVar waitingVar $! waiting Map.\\ toValidate
672-
let chunks Nothing xs = [xs]
673-
chunks (Just m) xs = map (take m) . takeWhile (not . null) . iterate (drop m) $ xs
674-
return . mapM_ parallelDelay . chunks npar . concat . Map.elems $ toValidate
620+
return ([processWaitingThread], add)
675621

676622
processWaiting' ::
677623
forall m a b.
678624
(MonadSTM m, MonadDelay m) =>
679625
TVar m (Map ConcreteHeaderHash a) ->
680-
TVar m (Map ConcreteHeaderHash [m b]) ->
626+
TVar m (Map ConcreteHeaderHash [STM m b]) ->
681627
m ()
682628
processWaiting' blocksVar waitingVar = go
683629
where
@@ -688,4 +634,4 @@ processWaiting' blocksVar waitingVar = go
688634
let toValidate = Map.intersection waiting blocks
689635
when (Map.null toValidate) retry
690636
writeTVar waitingVar $! waiting Map.\\ toValidate
691-
return . sequence_ . concat . Map.elems $ toValidate
637+
return . mapM_ atomically . concat . Map.elems $ toValidate
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,69 @@
11
{-# LANGUAGE BangPatterns #-}
2+
{-# LANGUAGE NamedFieldPuns #-}
3+
{-# LANGUAGE NondecreasingIndentation #-}
24
{-# LANGUAGE OverloadedRecordDot #-}
35
{-# LANGUAGE OverloadedStrings #-}
46
{-# LANGUAGE ScopedTypeVariables #-}
7+
{-# LANGUAGE TypeOperators #-}
58

69
module PraosProtocol.BlockGeneration where
710

811
import Cardano.Slotting.Slot (WithOrigin (..))
9-
import Control.Monad (forever)
12+
import Control.Monad.Trans
1013
import Control.Tracer
1114
import Data.ByteString as BS
1215
import Data.ByteString.Char8 as BS8
16+
import Data.Foldable (forM_)
1317
import Data.Function (fix)
14-
import Data.Word (Word64)
1518
import PraosProtocol.Common
1619
import qualified PraosProtocol.Common.Chain as Chain
1720
import STMCompat
18-
import System.Random (StdGen, uniformR)
21+
import System.Random (StdGen)
1922

2023
-- | Returns a block that can extend the chain.
2124
-- PRECONDITION: the SlotNo is ahead of the chain tip.
2225
mkBlock :: IsBody body => Chain (Block body) -> SlotNo -> body -> Block body
2326
mkBlock c sl body = fixupBlock (Chain.headAnchor c) (mkPartialBlock sl body)
2427

25-
type SlotGap = Word64
26-
27-
data PacketGenerationPattern
28-
= NoPacketGeneration
29-
| UniformGenerationPattern SlotGap
30-
| PoissonGenerationPattern StdGen Double
31-
3228
mkBody :: PraosConfig BlockBody -> ByteString -> SlotNo -> BlockBody
3329
mkBody cfg prefix (SlotNo w) = fix $ \b ->
3430
BlockBody
3531
{ bodyTag = BS.append prefix $ BS8.pack (show w)
3632
, bodyMessageSize = cfg.bodySize b
3733
}
3834

39-
mkNextBlock ::
40-
forall m.
41-
MonadSTM m =>
42-
PraosConfig BlockBody ->
43-
PacketGenerationPattern ->
44-
ByteString ->
45-
m (Maybe (m (SlotNo, BlockBody)))
46-
mkNextBlock _cfg NoPacketGeneration _ = return Nothing
47-
mkNextBlock cfg (UniformGenerationPattern gap) prefix = do
48-
stVar <- newTVarIO (SlotNo 0)
49-
let
50-
go = atomically $ do
51-
last_sl <- readTVar stVar
52-
let
53-
!sl = SlotNo (unSlotNo last_sl + gap :: Word64)
54-
writeTVar stVar sl
55-
let body = mkBody cfg prefix sl
56-
return (sl, body)
57-
return $ Just go
58-
mkNextBlock cfg (PoissonGenerationPattern rng0 lambda) prefix = do
59-
stVar <- newTVarIO (SlotNo 0, rng0)
60-
let go = atomically $ do
61-
(last_sl, rng) <- readTVar stVar
62-
63-
let (u, !rng') = uniformR (0, 1) rng
64-
gap = round ((-log u) * lambda :: Double) :: Word64
65-
66-
let !sl' = SlotNo $ unSlotNo last_sl + gap
67-
writeTVar stVar (sl', rng')
68-
let body = mkBody cfg prefix sl'
69-
return (sl', body)
70-
return $ Just go
71-
72-
blockGenerator ::
73-
(IsBody body, MonadSTM m, MonadDelay m, MonadTime m) =>
35+
praosBlockGenerator ::
36+
(IsBody body, MonadSTM m, MonadDelay m, MonadTime m, body ~ BlockBody) =>
37+
StdGen ->
7438
Tracer m (PraosNodeEvent body) ->
7539
PraosConfig body ->
7640
SlotConfig ->
41+
ByteString ->
7742
TVar m (ChainProducerState (Block body)) ->
7843
(Block body -> STM m ()) ->
79-
Maybe (m (SlotNo, body)) ->
44+
((CPUTask, m ()) -> m ()) ->
8045
m ()
81-
blockGenerator _tracer _praosConfig _ _cpsVar _addBlockSt Nothing = return ()
82-
blockGenerator tracer praosConfig slotConfig cpsVar addBlockSt (Just nextBlock) = forever go
46+
praosBlockGenerator rng tracer praosConfig slotConfig prefix cpsVar addBlockSt queue = do
47+
sched <- mkScheduler rng (const [((), Just $ \p -> if p <= praosConfig.blockFrequencyPerSlot then 1 else 0)])
48+
blockGenerator
49+
BlockGeneratorConfig{slotConfig, execute = execute sched}
8350
where
84-
go = do
85-
(sl, body) <- nextBlock
86-
waitForSlot sl
87-
let !delay = praosConfig.blockGenerationDelay $ mkPartialBlock sl body
88-
traceWith tracer (PraosNodeEventCPU $ CPUTask delay "Block generation")
89-
threadDelay delay
90-
mblk <- atomically $ do
91-
chain <- chainState <$> readTVar cpsVar
92-
let block = case mkBlock chain sl body of
93-
Block h b -> Block (h{headerMessageSize = praosConfig.headerSize}) b
94-
if Chain.headSlot chain <= At sl
95-
then addBlockSt block >> return (Just (block, chain))
96-
else return Nothing
97-
case mblk of
98-
Nothing -> return ()
99-
Just (blk, chain) -> do
100-
traceWith tracer (PraosNodeEventGenerate blk)
101-
traceWith tracer (PraosNodeEventNewTip (chain Chain.:> blk))
102-
waitForSlot sl = do
103-
let tgt = slotTime slotConfig sl
104-
now <- getCurrentTime
105-
threadDelayNDT (tgt `diffUTCTime` now)
51+
execute sched sl = lift $ do
52+
wins <- sched sl
53+
forM_ wins $ \_ -> do
54+
let body = mkBody praosConfig prefix sl
55+
let !delay = praosConfig.blockGenerationDelay $ mkPartialBlock sl body
56+
let !cpuTask = CPUTask delay "Block generation"
57+
curry queue cpuTask $ do
58+
mblk <- atomically $ do
59+
chain <- chainState <$> readTVar cpsVar
60+
let block = case mkBlock chain sl body of
61+
Block h b -> Block (h{headerMessageSize = praosConfig.headerSize}) b
62+
if Chain.headSlot chain <= At sl
63+
then addBlockSt block >> return (Just (block, chain))
64+
else return Nothing
65+
case mblk of
66+
Nothing -> return ()
67+
Just (blk, chain) -> do
68+
traceWith tracer (PraosNodeEventGenerate blk)
69+
traceWith tracer (PraosNodeEventNewTip (chain Chain.:> blk))

‎simulation/src/PraosProtocol/ChainSync.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ data ChainConsumerState m = ChainConsumerState
152152
}
153153

154154
runChainConsumer ::
155-
(MonadSTM m, MonadDelay m) =>
155+
MonadSTM m =>
156156
Tracer m (PraosNodeEvent body) ->
157157
PraosConfig body ->
158158
Chan m ChainSyncMessage ->
@@ -165,7 +165,7 @@ type ChainConsumer st m a = TC.Client ChainSyncState 'NonPipelined st m a
165165

166166
chainConsumer ::
167167
forall m body.
168-
(MonadSTM m, MonadDelay m) =>
168+
MonadSTM m =>
169169
Tracer m (PraosNodeEvent body) ->
170170
PraosConfig body ->
171171
ChainConsumerState m ->

‎simulation/src/PraosProtocol/Common.hs

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
{-# LANGUAGE BangPatterns #-}
12
{-# LANGUAGE NamedFieldPuns #-}
23
{-# LANGUAGE RecordWildCards #-}
34
{-# LANGUAGE ScopedTypeVariables #-}
45
{-# LANGUAGE TypeOperators #-}
6+
{-# LANGUAGE NoFieldSelectors #-}
57
{-# OPTIONS_GHC -Wno-orphans #-}
68

79
module PraosProtocol.Common (
@@ -31,22 +33,29 @@ module PraosProtocol.Common (
3133
defaultPraosConfig,
3234
CPUTask (..),
3335
hashToColor,
36+
blockGenerator,
37+
BlockGeneratorConfig (..),
38+
waitNextSlot,
39+
mkScheduler,
3440
) where
3541

3642
import ChanTCP (Bytes, MessageSize (..))
3743
import Control.Exception (assert)
44+
import Control.Monad.State
3845
import Data.Coerce (coerce)
3946
import Data.Default
4047
import Data.Map.Strict (Map)
4148
import qualified Data.Map.Strict as Map
4249
import Data.Word (Word8)
50+
import GHC.Word (Word64)
4351
import Ouroboros.Network.Mock.ProducerState as ProducerState
4452
import PraosProtocol.Common.AnchoredFragment (Anchor (..), AnchoredFragment)
4553
import PraosProtocol.Common.Chain (Chain (..), foldChain, pointOnChain)
4654
import PraosProtocol.Common.ConcreteBlock as ConcreteBlock
55+
import STMCompat
4756
import SimTCPLinks (kilobytes)
4857
import SimTypes (CPUTask (..))
49-
import System.Random (mkStdGen, uniform)
58+
import System.Random (StdGen, mkStdGen, uniform, uniformR)
5059
import TimeCompat
5160

5261
--------------------------------
@@ -158,3 +167,55 @@ defaultPraosConfig =
158167

159168
instance Default (PraosConfig body) where
160169
def = defaultPraosConfig
170+
171+
data BlockGeneratorConfig m = BlockGeneratorConfig
172+
{ slotConfig :: SlotConfig
173+
, execute :: SlotNo -> StateT Int m ()
174+
}
175+
176+
blockGenerator ::
177+
forall m.
178+
(MonadSTM m, MonadDelay m, MonadTime m) =>
179+
BlockGeneratorConfig m ->
180+
m ()
181+
blockGenerator BlockGeneratorConfig{..} = go (0, 0)
182+
where
183+
go (!blkId, !tgtSlot) = do
184+
slot <- waitNextSlot slotConfig tgtSlot
185+
blkId' <- execStateT (execute slot) blkId
186+
go (blkId', slot + 1)
187+
188+
-- | @waitNextSlot cfg targetSlot@ waits until the beginning of
189+
-- @targetSlot@ if that's now or in the future, otherwise the closest slot.
190+
waitNextSlot :: (Monad m, MonadTime m, MonadDelay m) => SlotConfig -> SlotNo -> m SlotNo
191+
waitNextSlot slotConfig targetSlot = do
192+
now <- getCurrentTime
193+
let targetSlotTime = slotTime slotConfig targetSlot
194+
let slot
195+
| now <= targetSlotTime = targetSlot
196+
| otherwise = assert (nextSlotIndex >= 0) $ toEnum nextSlotIndex
197+
where
198+
nextSlotIndex =
199+
assert (slotConfig.duration == 1) $
200+
ceiling $
201+
now `diffUTCTime` slotConfig.start
202+
let tgt = slotTime slotConfig slot
203+
threadDelayNDT (tgt `diffUTCTime` now)
204+
return slot
205+
206+
mkScheduler :: MonadSTM m => StdGen -> (SlotNo -> [(a, Maybe (Double -> Word64))]) -> m (SlotNo -> m [(a, Word64)])
207+
mkScheduler rng0 rates = do
208+
let
209+
sampleRates (_role, Nothing) = return []
210+
sampleRates (role, Just f) = do
211+
(sample, rng') <- gets $ uniformR (0, 1)
212+
put $! rng'
213+
let wins = f sample
214+
return [(role, wins) | wins >= 1]
215+
rngVar <- newTVarIO rng0
216+
let sched slot = atomically $ do
217+
rng <- readTVar rngVar
218+
let (acts, rng1) = flip runState rng . fmap concat . mapM sampleRates $ rates slot
219+
writeTVar rngVar rng1
220+
return acts
221+
return sched

‎simulation/src/PraosProtocol/ExamplesPraosP2P.hs

+13-25
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import qualified LeiosProtocol.Config as OnDisk
3030
import Network.TypedProtocol
3131
import P2P (P2PTopography, P2PTopographyCharacteristics (..), genArbitraryP2PTopography, p2pNodes)
3232
import PraosProtocol.BlockFetch
33-
import PraosProtocol.BlockGeneration (PacketGenerationPattern (..))
3433
import PraosProtocol.Common
3534
import qualified PraosProtocol.Common.Chain as Chain
3635
import PraosProtocol.PraosNode
@@ -263,7 +262,7 @@ example1000Diffusion ::
263262
-- | file to write data to.
264263
FilePath ->
265264
IO ()
266-
example1000Diffusion rng0 cfg p2pNetwork@P2P.P2PNetwork{p2pNodes, p2pNodeStakes} stop fp
265+
example1000Diffusion rng0 cfg p2pNetwork@P2P.P2PNetwork{p2pNodeStakes, p2pNodeCores} stop fp
267266
| length (List.group (Map.elems p2pNodeStakes)) /= 1 = error "Only uniform stake distribution supported for this sim."
268267
| otherwise =
269268
runSampleModel traceFile logEvent (diffusionSampleModel (P2P.networkToTopology p2pNetwork) fp) stop $
@@ -292,50 +291,42 @@ example1000Diffusion rng0 cfg p2pNetwork@P2P.P2PNetwork{p2pNodes, p2pNodeStakes}
292291
logMsg ((PraosMessage _)) = Nothing
293292

294293
traceFile = dropExtension fp <.> "log"
295-
blockInterval = 1 / praosConfig.blockFrequencyPerSlot
296294
praosConfig = maybe defaultPraosConfig convertConfig cfg
297-
p2pNumNodes = Map.size $ p2pNodes
295+
stake nid = maybe undefined coerce $ Map.lookup nid p2pNodeStakes
298296
trace =
299297
tracePraosP2P
300298
rng0
301299
p2pNetwork
302300
(\latency -> mkTcpConnProps latency . fromMaybe (error "Only finite bandwidth supported for this sim."))
303301
( \slotConfig nid rng ->
304302
PraosNodeConfig
305-
{ blockGeneration =
306-
PoissonGenerationPattern
307-
rng
308-
-- average seconds between blocks:
309-
(realToFrac blockInterval * fromIntegral p2pNumNodes)
310-
, praosConfig
303+
{ rng
304+
, praosConfig = praosConfig{blockFrequencyPerSlot = praosConfig.blockFrequencyPerSlot * stake nid}
311305
, blockMarker = BS8.pack $ show nid ++ ": "
312306
, chain = Genesis
313307
, slotConfig
308+
, processingCores = fromMaybe undefined $ Map.lookup nid p2pNodeCores
314309
}
315310
)
316311

317312
example1Trace :: StdGen -> PraosConfig BlockBody -> P2P.P2PNetwork -> PraosTrace
318-
example1Trace rng0 praosConfig p2pNetwork@P2P.P2PNetwork{p2pNodes} =
313+
example1Trace rng0 praosConfig p2pNetwork@P2P.P2PNetwork{p2pNodeStakes, p2pNodeCores} =
319314
tracePraosP2P
320315
rng0
321316
p2pNetwork
322317
(\latency -> mkTcpConnProps latency . fromMaybe undefined)
323318
( \slotConfig nid rng ->
324319
PraosNodeConfig
325-
{ blockGeneration =
326-
PoissonGenerationPattern
327-
rng
328-
-- average seconds between blocks:
329-
(realToFrac blockInterval * fromIntegral p2pNumNodes)
320+
{ rng
330321
, slotConfig
331-
, praosConfig
322+
, praosConfig = praosConfig{blockFrequencyPerSlot = praosConfig.blockFrequencyPerSlot * stake nid}
332323
, blockMarker = BS8.pack $ show nid ++ ": "
333324
, chain = Genesis
325+
, processingCores = fromMaybe undefined $ Map.lookup nid p2pNodeCores
334326
}
335327
)
336328
where
337-
blockInterval = 1 / praosConfig.blockFrequencyPerSlot
338-
p2pNumNodes = Map.size p2pNodes
329+
stake nid = maybe undefined coerce $ Map.lookup nid p2pNodeStakes
339330

340331
example2 :: Visualization
341332
example2 =
@@ -401,15 +392,12 @@ example2 =
401392
(\latency -> mkTcpConnProps latency . fromMaybe undefined)
402393
( \slotConfig nid rng ->
403394
PraosNodeConfig
404-
{ blockGeneration =
405-
PoissonGenerationPattern
406-
rng
407-
-- average seconds between blocks:
408-
(5 * fromIntegral p2pNumNodes)
409-
, praosConfig = defaultPraosConfig
395+
{ rng
396+
, praosConfig = defaultPraosConfig{blockFrequencyPerSlot = 5 / fromIntegral p2pNumNodes}
410397
, slotConfig
411398
, chain = Genesis
412399
, blockMarker = BS8.pack $ show nid ++ ": "
400+
, processingCores = Finite 1
413401
}
414402
)
415403
p2pTopography =

‎simulation/src/PraosProtocol/PraosNode.hs

+29-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import ChanDriver (protocolMessage)
1414
import ChanMux
1515
import Control.Exception (assert)
1616
import Control.Monad.Class.MonadAsync (Concurrently (..), MonadAsync (..))
17-
import Control.Tracer (Tracer, traceWith)
17+
import Control.Monad.Class.MonadFork
18+
import Control.Monad.Class.MonadThrow
19+
import Control.Tracer (Tracer, contramap)
1820
import Data.ByteString (ByteString)
1921
import Data.Coerce (coerce)
2022
import Data.Either (fromLeft, fromRight)
@@ -28,6 +30,9 @@ import PraosProtocol.ChainSync (ChainConsumerState (..), ChainSyncMessage, chain
2830
import PraosProtocol.Common
2931
import qualified PraosProtocol.Common.Chain as Chain (Chain (..))
3032
import STMCompat
33+
import SimTypes
34+
import System.Random (StdGen)
35+
import TaskMultiQueue
3136

3237
data Praos body f = Praos
3338
{ protocolChainSync :: f ChainSyncMessage
@@ -135,7 +140,9 @@ runPraosNode ::
135140
m ()
136141
runPraosNode tracer cfg chain followers peers = do
137142
st0 <- PraosNodeState <$> newBlockFetchControllerState chain <*> pure Map.empty
138-
concurrentlyMany . map runConcurrently =<< setupPraosThreads tracer cfg st0 followers peers
143+
taskQueue <- atomically $ newTaskMultiQueue @() 100
144+
let queue = writeTMQueue taskQueue ()
145+
concurrentlyMany . map runConcurrently =<< setupPraosThreads tracer cfg queue st0 followers peers
139146
where
140147
-- Nested children threads are slow with IOSim, this impl forks them all as direct children.
141148
concurrentlyMany :: MonadAsync m => [m ()] -> m ()
@@ -145,16 +152,20 @@ setupPraosThreads ::
145152
(MonadAsync m, MonadSTM m, MonadDelay m) =>
146153
Tracer m (PraosNodeEvent BlockBody) ->
147154
PraosConfig BlockBody ->
155+
((CPUTask, m ()) -> STM m ()) ->
148156
PraosNodeState BlockBody m ->
149157
[Praos BlockBody (Chan m)] ->
150158
[Praos BlockBody (Chan m)] ->
151159
m [Concurrently m ()]
152-
setupPraosThreads tracer cfg st0 followers peers = do
153-
(ts, f) <- BlockFetch.setupValidatorThreads tracer cfg st0.blockFetchControllerState 1 -- TODO: parameter
160+
setupPraosThreads tracer cfg queue st0 followers peers = do
161+
(ts, f) <- BlockFetch.setupValidatorThreads cfg st0.blockFetchControllerState queue
154162
let valHeader h = assert (blockInvariant h) $ do
155163
let !delay = cfg.headerValidationDelay h
156-
traceWith tracer (PraosNodeEventCPU (CPUTask delay $ T.pack $ "ValidateHeader " ++ show (coerce @_ @Int $ blockHash h)))
157-
threadDelay delay
164+
atomically $
165+
curry
166+
queue
167+
(CPUTask delay $ T.pack $ "ValidateHeader " ++ show (coerce @_ @Int $ blockHash h))
168+
(return ())
158169
(map Concurrently ts ++) <$> setupPraosThreads' tracer cfg valHeader f st0 followers peers
159170

160171
setupPraosThreads' ::
@@ -178,32 +189,37 @@ setupPraosThreads' tracer cfg valHeader submitFetchedBlock st0 followers peers =
178189
data PraosNodeConfig = PraosNodeConfig
179190
{ praosConfig :: PraosConfig BlockBody
180191
, slotConfig :: SlotConfig
181-
, blockGeneration :: PacketGenerationPattern
182192
, chain :: Chain (Block BlockBody)
183193
, blockMarker :: ByteString
184194
-- ^ bytes to include in block bodies.
195+
, rng :: StdGen
196+
, processingCores :: NumCores
185197
}
186198

187199
newPraosNodeState :: MonadSTM m => Chain (Block body) -> m (PraosNodeState body m)
188200
newPraosNodeState chain = PraosNodeState <$> newBlockFetchControllerState chain <*> pure Map.empty
189201

190202
praosNode ::
191-
(MonadAsync m, MonadSTM m, MonadTime m, MonadDelay m) =>
203+
(MonadAsync m, MonadSTM m, MonadTime m, MonadDelay m, MonadCatch m, MonadFork m, MonadMonotonicTimeNSec m) =>
192204
Tracer m (PraosNodeEvent BlockBody) ->
193205
PraosNodeConfig ->
194206
[Praos BlockBody (Chan m)] ->
195207
[Praos BlockBody (Chan m)] ->
196208
m [m ()]
197209
praosNode tracer cfg followers peers = do
198210
st0 <- PraosNodeState <$> newBlockFetchControllerState cfg.chain <*> pure Map.empty
199-
praosThreads <- setupPraosThreads tracer cfg.praosConfig st0 followers peers
200-
nextBlock <- mkNextBlock cfg.praosConfig cfg.blockGeneration cfg.blockMarker
211+
taskQueue <- atomically $ newTaskMultiQueue @() 100
212+
let queue = writeTMQueue taskQueue ()
213+
praosThreads <- setupPraosThreads tracer cfg.praosConfig queue st0 followers peers
214+
let cpuTasksProcessors = processCPUTasks cfg.processingCores (contramap PraosNodeEventCPU tracer) taskQueue
201215
let generationThread =
202-
blockGenerator
216+
praosBlockGenerator
217+
cfg.rng
203218
tracer
204219
cfg.praosConfig
205220
cfg.slotConfig
221+
cfg.blockMarker
206222
st0.blockFetchControllerState.cpsVar
207223
(BlockFetch.addProducedBlock st0.blockFetchControllerState)
208-
nextBlock
209-
return $ map runConcurrently $ Concurrently generationThread : praosThreads
224+
(atomically . queue)
225+
return $ cpuTasksProcessors : generationThread : map runConcurrently praosThreads

‎simulation/src/PraosProtocol/SimBlockFetch.hs

+14-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module PraosProtocol.SimBlockFetch where
55
import Chan (Chan)
66
import ChanDriver (ProtocolMessage)
77
import ChanTCP
8+
import Control.Monad
89
import Control.Monad.Class.MonadAsync (
910
MonadAsync (..),
1011
mapConcurrently_,
@@ -82,14 +83,19 @@ traceRelayLink1 tcpprops =
8283
nodeA praosConfig chan = do
8384
peerChainVar <- newTVarIO (blockHeader <$> bchain)
8485
(st, peerId) <- newBlockFetchControllerState Genesis >>= addPeer (asReadOnly peerChainVar)
85-
(ts, submitFetchedBlock) <- setupValidatorThreads nullTracer praosConfig st 1
86-
concurrently_ (mapConcurrently_ id ts) $
87-
concurrently_
88-
( blockFetchController nullTracer st
89-
)
90-
( runBlockFetchConsumer nullTracer praosConfig chan $
91-
initBlockFetchConsumerStateForPeerId nullTracer peerId st submitFetchedBlock
92-
)
86+
taskTMVar <- newEmptyTMVarIO
87+
let queue (_, t) = putTMVar taskTMVar t
88+
let processingThread = forever $ do
89+
join $ atomically $ takeTMVar taskTMVar
90+
(ts, submitFetchedBlock) <- setupValidatorThreads praosConfig st queue
91+
concurrently_ processingThread $
92+
concurrently_ (mapConcurrently_ id ts) $
93+
concurrently_
94+
( blockFetchController nullTracer st
95+
)
96+
( runBlockFetchConsumer nullTracer praosConfig chan $
97+
initBlockFetchConsumerStateForPeerId nullTracer peerId st submitFetchedBlock
98+
)
9399
-- Block-Fetch Producer
94100
nodeB chan = do
95101
st <- BlockFetchProducerState . asReadOnly <$> newTVarIO (toBlocks bchain)

‎simulation/src/LeiosProtocol/TaskMultiQueue.hs ‎simulation/src/TaskMultiQueue.hs

+26-3
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
{-# LANGUAGE RecordWildCards #-}
55
{-# LANGUAGE TupleSections #-}
66

7-
module LeiosProtocol.TaskMultiQueue where
7+
module TaskMultiQueue where
88

99
import Control.Monad
10+
import Control.Monad.Class.MonadAsync
1011
import Control.Monad.Class.MonadFork (MonadFork (forkIO))
12+
import Control.Monad.Class.MonadThrow
1113
import Control.Tracer
1214
import Data.Array
1315
import qualified Data.Map.Strict as Map
1416
import GHC.Natural
15-
import LeiosProtocol.Common
1617
import STMCompat
18+
import SimTypes
19+
import TimeCompat
20+
import WorkerPool
1721

1822
type IsLabel lbl = (Ix lbl, Bounded lbl)
1923

@@ -23,7 +27,7 @@ newTaskMultiQueue' :: (MonadSTM m, Ix l) => (l, l) -> Natural -> STM m (TaskMult
2327
newTaskMultiQueue' (a, b) n =
2428
TaskMultiQueue . listArray (a, b) <$> mapM (const $ newTBQueue n) (range (a, b))
2529

26-
newTaskMultiQueue :: (MonadSTM m, IsLabel l) => Natural -> STM m (TaskMultiQueue l m)
30+
newTaskMultiQueue :: forall l m. (MonadSTM m, IsLabel l) => Natural -> STM m (TaskMultiQueue l m)
2731
newTaskMultiQueue = newTaskMultiQueue' (minBound, maxBound)
2832

2933
writeTMQueue :: (MonadSTM m, IsLabel l) => TaskMultiQueue l m -> l -> (CPUTask, m ()) -> STM m ()
@@ -58,3 +62,22 @@ runInfParallelBlocking tracer mq = do
5862
forM_ (Map.toAscList tasksByEnd) $ \(end, ms) -> do
5963
waitUntil end
6064
sequence_ ms
65+
66+
processCPUTasks ::
67+
(MonadSTM m, MonadDelay m, MonadMonotonicTimeNSec m, MonadFork m, MonadAsync m, MonadCatch m, IsLabel lbl) =>
68+
NumCores ->
69+
Tracer m CPUTask ->
70+
TaskMultiQueue lbl m ->
71+
m ()
72+
processCPUTasks Infinite tracer queue = forever $ runInfParallelBlocking tracer queue
73+
processCPUTasks (Finite n) tracer queue = newBoundedWorkerPool n [taskSource l | l <- range (minBound, maxBound)]
74+
where
75+
taskSource l = do
76+
(cpu, m) <- readTMQueue queue l
77+
var <- newEmptyTMVar
78+
let action = do
79+
traceWith tracer cpu
80+
threadDelay (cpuTaskDuration cpu)
81+
m
82+
-- TODO: read from var and log exception.
83+
return $ Task action var

0 commit comments

Comments
 (0)
Please sign in to comment.