1+ {-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE BlockArguments #-}
3+ {-# LANGUAGE DeriveAnyClass #-}
4+ {-# LANGUAGE DeriveGeneric #-}
25{-# LANGUAGE DerivingVia #-}
36{-# LANGUAGE ImportQualifiedPost #-}
47{-# LANGUAGE NamedFieldPuns #-}
@@ -14,25 +17,29 @@ module Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.Inbound.V2.Decision
1417 , mkDecisionContext
1518 ) where
1619
20+ import Cardano.Prelude (for )
1721import Control.DeepSeq (NFData (.. ))
22+ import Data.Either (partitionEithers )
1823import Data.Foldable qualified as Foldable
19- import Data.Hashable (Hashable (.. ))
2024import Data.Map.Merge.Strict qualified as Map
2125import Data.Map.Strict (Map )
2226import Data.Map.Strict qualified as Map
27+ import Data.Maybe (fromMaybe )
2328import Data.Sequence.Strict (StrictSeq )
2429import Data.Sequence.Strict qualified as StrictSeq
2530import Data.Set (Set )
2631import Data.Set qualified as Set
32+ import GHC.Generics (Generic )
2733import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.Inbound.V2.Policy
2834import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.Inbound.V2.State
2935import Ouroboros.Consensus.MiniProtocol.ObjectDiffusion.Inbound.V2.Types
3036import Ouroboros.Network.Protocol.ObjectDiffusion.Type
37+ import System.Random (StdGen , mkStdGen )
38+ import System.Random.SplitMix (SMGen )
3139import Test.QuickCheck (Arbitrary (.. ))
32- import Test.QuickCheck.Gen (Gen (.. ))
40+ import Test.QuickCheck.Arbitrary (vector )
41+ import Test.QuickCheck.Gen (Gen (.. ), choose , shuffle )
3342import Test.QuickCheck.Random (QCGen (.. ))
34- import System.Random.SplitMix (SMGen , nextInt )
35- import System.Random (StdGen , mkStdGen )
3643
3744data DecisionContext peerAddr objectId object = DecisionContext
3845 { dcRng :: StdGen
@@ -41,49 +48,108 @@ data DecisionContext peerAddr objectId object = DecisionContext
4148 , dcGlobalState :: DecisionGlobalState peerAddr objectId object
4249 , dcPrevDecisions :: Map peerAddr (PeerDecision objectId object )
4350 }
51+ deriving stock Generic
52+ deriving anyclass NFData
53+
54+ -- TODO: Using `sized` to control size, we could maybe provide directly an instance of Arbitrary for DecisionContext?
55+
56+ partitionWithProb :: Double -> [a ] -> Gen ([a ], [a ])
57+ partitionWithProb p xs = do
58+ partitionEithers
59+ <$> traverse
60+ ( \ x -> do
61+ r <- choose (0.0 , 1.0 )
62+ if r < p then return (Left x) else return (Right x)
63+ )
64+ xs
4465
45- instance
46- ( NFData peerAddr
47- , NFData objectId
48- , NFData object
49- ) =>
50- NFData (DecisionContext peerAddr objectId object ) where
51- rnf = undefined
52-
53- -- TODO: do not generate dcDecisionPolicy arbitrarily, it makes little sense.
54- -- Instead we should provide decision policies fit for the concrete object types
55- -- we want to make decisions for.
66+ -- TODO: we do not take into account dpMaxNumObjectsInflightTotal here
5667mkDecisionContext ::
5768 forall peerAddr objectId object .
5869 ( Arbitrary peerAddr
59- , Arbitrary objectId
6070 , Arbitrary object
6171 , Ord peerAddr
6272 , Ord objectId
63- , Hashable objectId
6473 ) =>
6574 SMGen ->
6675 Int ->
76+ Int ->
77+ (object -> objectId ) ->
78+ -- | If we want to provide a specific decision policy instead of relying on an arbitrary variation of the default one
79+ Maybe DecisionPolicy ->
6780 DecisionContext peerAddr objectId object
68- mkDecisionContext stdGen size = unGen gen (QCGen stdGen') size
69- where
70- (salt, stdGen') = nextInt stdGen
71- gen :: Gen (DecisionContext peerAddr objectId object )
72- gen = do
73- dcRng <- mkStdGen <$> arbitrary
74- dcDecisionPolicy <- arbitrary
75- dcGlobalState <- arbitrary
76- dcPrevDecisions <- arbitrary
77- let dcHasObject objId =
78- hashWithSalt salt objId `mod` 2 == 0
79- pure $ DecisionContext
81+ mkDecisionContext stdGen peersNb objectsNb getId mPolicy = unGen gen (QCGen stdGen) peersNb -- We use peerNb as the size parameter
82+ where
83+ alreadyInPoolRatio :: Double = 0.2
84+ -- How many peers should offer an object compared to the target redundancy
85+ -- for objects (e.g. with targetRedundancy at 2, and this factor at 1.5, then 3 peers will on average offer each object)
86+ advertiseRedundancyOverTargetRedundancy :: Double = 1.5
87+
88+ gen :: Gen (DecisionContext peerAddr objectId object )
89+ gen = do
90+ dcRng <- mkStdGen <$> arbitrary
91+ dcDecisionPolicy@ DecisionPolicy {dpTargetObjectRedundancy} <- fromMaybe arbitrary (pure <$> mPolicy)
92+ objects <- vector objectsNb
93+
94+ (alreadyInPool, _) <- partitionWithProb alreadyInPoolRatio objects
95+ let ! alreadyInPoolIds = Set. fromList $ getId <$> alreadyInPool
96+ dcHasObject = (`Set.member` alreadyInPoolIds)
97+
98+ dcGlobalState <-
99+ DecisionGlobalState . Map. fromList <$> for [1 .. peersNb] \ _ -> do
100+ (peerObjects, _) <-
101+ partitionWithProb
102+ ( advertiseRedundancyOverTargetRedundancy
103+ * fromIntegral dpTargetObjectRedundancy
104+ / fromIntegral peersNb
105+ )
106+ objects
107+ (,) <$> arbitrary <*> (mkPeerState getId dcDecisionPolicy peerObjects)
108+
109+ let dcPrevDecisions = Map. map (\ _ -> unavailableDecision) (dgsPeerStates dcGlobalState)
110+
111+ pure $
112+ DecisionContext
80113 { dcRng
81114 , dcHasObject
82115 , dcDecisionPolicy
83116 , dcGlobalState
84117 , dcPrevDecisions
85118 }
86119
120+ mkPeerState ::
121+ Ord objectId =>
122+ (object -> objectId ) -> DecisionPolicy -> [object ] -> Gen (DecisionPeerState objectId object )
123+ mkPeerState getId DecisionPolicy {dpMaxNumObjectsOutstanding, dpMaxNumObjectsInflightPerPeer} rawPeerObjects = do
124+ let peerObjects = take (fromIntegral dpMaxNumObjectsOutstanding) rawPeerObjects
125+
126+ let inflightRatio :: Double = 0.1
127+ let owtPoolRatio :: Double = 0.1
128+ -- let availableRatio :: Double = 1 - (inflightRatio + owtPoolRatio)
129+ let owtPoolStillInFifoRatio :: Double = 0.3
130+ let idsInflightSaturation :: Double = 0.8
131+
132+ (objectsAvailable, rest) <- partitionWithProb (1 - (inflightRatio + owtPoolRatio)) peerObjects
133+ (rawObjectsInflight, objectsOwtPool) <-
134+ partitionWithProb (owtPoolRatio / (owtPoolRatio + inflightRatio)) rest
135+ let objectsInflight = take (fromIntegral dpMaxNumObjectsInflightPerPeer) rawObjectsInflight
136+
137+ (owtPoolStillInFifo, _) <- partitionWithProb owtPoolStillInFifoRatio objectsOwtPool
138+ objectsInFifo <- shuffle $ objectsAvailable ++ objectsInflight ++ owtPoolStillInFifo
139+
140+ let maxNumIdsInFlight = max 0 (fromIntegral dpMaxNumObjectsOutstanding - length objectsInFifo)
141+ numIdsInFlight <-
142+ (chooseGeometricWithMedian (round $ idsInflightSaturation * fromIntegral maxNumIdsInFlight))
143+
144+ pure $
145+ DecisionPeerState
146+ { dpsObjectsAvailableIds = Set. fromList $ getId <$> objectsAvailable
147+ , dpsObjectsInflightIds = Set. fromList $ getId <$> objectsInflight
148+ , dpsObjectsOwtPool = Map. fromList $ (\ obj -> (getId obj, obj)) <$> objectsOwtPool
149+ , dpsOutstandingFifo = StrictSeq. fromList $ getId <$> objectsInFifo
150+ , dpsNumIdsInflight = fromIntegral $ numIdsInFlight `max` 0 `min` maxNumIdsInFlight
151+ }
152+
87153strictSeqToSet :: Ord a => StrictSeq a -> Set a
88154strictSeqToSet = Set. fromList . Foldable. toList
89155
@@ -93,25 +159,25 @@ makeDecisions ::
93159 ( Ord peerAddr
94160 , Ord objectId
95161 ) =>
96- StdGen ->
97- (objectId -> Bool ) ->
98- -- | decision decisionPolicy
99- DecisionPolicy ->
100- -- | decision context
101- DecisionGlobalState peerAddr objectId object ->
102- -- | Previous decisions
103- Map peerAddr (PeerDecision objectId object ) ->
162+ DecisionContext peerAddr objectId object ->
104163 -- | New decisions
105164 Map peerAddr (PeerDecision objectId object )
106- makeDecisions rng hasObject decisionPolicy globalState prevDecisions =
165+ makeDecisions DecisionContext {dcRng, dcHasObject, dcDecisionPolicy, dcGlobalState, dcPrevDecisions} =
107166 let
108167 -- A subset of peers are currently executing a decision. We shouldn't update the decision for them
109- frozenPeersToDecisions = Map. filter (\ PeerDecision {pdStatus} -> pdStatus == DecisionBeingActedUpon ) prevDecisions
168+ frozenPeersToDecisions = Map. filter (\ PeerDecision {pdStatus} -> pdStatus == DecisionBeingActedUpon ) dcPrevDecisions
110169
111170 -- We do it in two steps, because computing the acknowledgment tell which objects from dpsObjectsAvailableIds sets of each peer won't actually be available anymore (as soon as we ack them),
112171 -- so that the pickObjectsToReq function can take this into account.
113- (ackAndRequestIdsDecisions, peerToIdsToAck) = computeAck hasObject decisionPolicy globalState frozenPeersToDecisions
114- peersToObjectsToReq = pickObjectsToReq rng hasObject decisionPolicy globalState frozenPeersToDecisions peerToIdsToAck
172+ (ackAndRequestIdsDecisions, peerToIdsToAck) = computeAck dcHasObject dcDecisionPolicy dcGlobalState frozenPeersToDecisions
173+ peersToObjectsToReq =
174+ pickObjectsToReq
175+ dcRng
176+ dcHasObject
177+ dcDecisionPolicy
178+ dcGlobalState
179+ frozenPeersToDecisions
180+ peerToIdsToAck
115181 in
116182 Map. intersectionWith
117183 (\ decision objectsToReqIds -> decision{pdObjectsToReqIds = objectsToReqIds})
0 commit comments