55{-# LANGUAGE ScopedTypeVariables #-}
66{-# LANGUAGE TypeOperators #-}
77
8+ -- | Data structure for tracking the weight of blocks due to Peras boosts.
89module Ouroboros.Consensus.Peras.Weight
9- ( -- * 'PerasWeightSnapshot'
10+ ( -- * 'PerasWeightSnapshot' type
1011 PerasWeightSnapshot
12+
13+ -- * Construction
1114 , emptyPerasWeightSnapshot
1215 , mkPerasWeightSnapshot
16+
17+ -- * Conversion
1318 , perasWeightSnapshotToList
19+
20+ -- * Insertion
1421 , addToPerasWeightSnapshot
22+
23+ -- * Pruning
1524 , prunePerasWeightSnapshot
25+
26+ -- * Query
1627 , weightBoostOfPoint
1728 , weightBoostOfFragment
1829 ) where
@@ -26,16 +37,37 @@ import Ouroboros.Consensus.Block
2637import Ouroboros.Network.AnchoredFragment (AnchoredFragment )
2738import qualified Ouroboros.Network.AnchoredFragment as AF
2839
40+ -- | Data structure for tracking the weight of blocks due to Peras boosts.
2941newtype PerasWeightSnapshot blk = PerasWeightSnapshot
3042 { getPerasWeightSnapshot :: Map (Point blk ) PerasWeight
3143 }
32- deriving stock ( Show , Eq )
44+ deriving stock Eq
3345 deriving Generic
3446 deriving newtype NoThunks
3547
48+ instance StandardHash blk => Show (PerasWeightSnapshot blk ) where
49+ show = show . perasWeightSnapshotToList
50+
51+ -- | An empty 'PerasWeightSnapshot' not containing any boosted blocks.
3652emptyPerasWeightSnapshot :: PerasWeightSnapshot blk
3753emptyPerasWeightSnapshot = PerasWeightSnapshot Map. empty
3854
55+ -- | Create a weight snapshot from a list of boosted points with an associated
56+ -- weight. In case of duplicate points, their weights are combined.
57+ --
58+ -- >>> :{
59+ -- weights :: [(Point Blk, PerasWeight)]
60+ -- weights =
61+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
62+ -- , (GenesisPoint, PerasWeight 3)
63+ -- , (BlockPoint 3 "bar", PerasWeight 2)
64+ -- , (BlockPoint 2 "foo", PerasWeight 2)
65+ -- ]
66+ -- :}
67+ --
68+ -- >>> snap = mkPerasWeightSnapshot weights
69+ -- >>> snap
70+ -- [(Origin,PerasWeight 3),(At (Block {blockPointSlot = SlotNo 2, blockPointHash = "foo"}),PerasWeight 4),(At (Block {blockPointSlot = SlotNo 3, blockPointHash = "bar"}),PerasWeight 2)]
3971mkPerasWeightSnapshot ::
4072 StandardHash blk =>
4173 [(Point blk , PerasWeight )] ->
@@ -45,9 +77,47 @@ mkPerasWeightSnapshot =
4577 (\ s (pt, weight) -> addToPerasWeightSnapshot pt weight s)
4678 emptyPerasWeightSnapshot
4779
80+ -- | Return the list of boosted points with their associated weight, sorted
81+ -- based on their point. Does not contain duplicate points.
82+ --
83+ -- >>> :{
84+ -- weights :: [(Point Blk, PerasWeight)]
85+ -- weights =
86+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
87+ -- , (GenesisPoint, PerasWeight 3)
88+ -- , (BlockPoint 3 "bar", PerasWeight 2)
89+ -- , (BlockPoint 2 "foo", PerasWeight 2)
90+ -- ]
91+ -- :}
92+ --
93+ -- >>> snap = mkPerasWeightSnapshot weights
94+ -- >>> perasWeightSnapshotToList snap
95+ -- [(Origin,PerasWeight 3),(At (Block {blockPointSlot = SlotNo 2, blockPointHash = "foo"}),PerasWeight 4),(At (Block {blockPointSlot = SlotNo 3, blockPointHash = "bar"}),PerasWeight 2)]
4896perasWeightSnapshotToList :: PerasWeightSnapshot blk -> [(Point blk , PerasWeight )]
49- perasWeightSnapshotToList = Map. toList . getPerasWeightSnapshot
97+ perasWeightSnapshotToList = Map. toAscList . getPerasWeightSnapshot
5098
99+ -- | Add weight for the given point to the 'PerasWeightSnapshot'. If the point
100+ -- already has some weight, it is added on top.
101+ --
102+ -- >>> :{
103+ -- weights :: [(Point Blk, PerasWeight)]
104+ -- weights =
105+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
106+ -- , (GenesisPoint, PerasWeight 3)
107+ -- ]
108+ -- :}
109+ --
110+ -- >>> snap0 = mkPerasWeightSnapshot weights
111+ -- >>> snap0
112+ -- [(Origin,PerasWeight 3),(At (Block {blockPointSlot = SlotNo 2, blockPointHash = "foo"}),PerasWeight 2)]
113+ --
114+ -- >>> snap1 = addToPerasWeightSnapshot (BlockPoint 3 "bar") (PerasWeight 2) snap0
115+ -- >>> snap1
116+ -- [(Origin,PerasWeight 3),(At (Block {blockPointSlot = SlotNo 2, blockPointHash = "foo"}),PerasWeight 2),(At (Block {blockPointSlot = SlotNo 3, blockPointHash = "bar"}),PerasWeight 2)]
117+ --
118+ -- >>> snap2 = addToPerasWeightSnapshot (BlockPoint 2 "foo") (PerasWeight 2) snap1
119+ -- >>> snap2
120+ -- [(Origin,PerasWeight 3),(At (Block {blockPointSlot = SlotNo 2, blockPointHash = "foo"}),PerasWeight 4),(At (Block {blockPointSlot = SlotNo 3, blockPointHash = "bar"}),PerasWeight 2)]
51121addToPerasWeightSnapshot ::
52122 StandardHash blk =>
53123 Point blk ->
@@ -57,6 +127,29 @@ addToPerasWeightSnapshot ::
57127addToPerasWeightSnapshot pt weight =
58128 PerasWeightSnapshot . Map. insertWith (<>) pt weight . getPerasWeightSnapshot
59129
130+ -- | Prune the given 'PerasWeightSnapshot' by removing the weight of all blocks
131+ -- strictly older than the given slot.
132+ --
133+ -- This function is used to get garbage-collect boosted blocks blocks which are
134+ -- older than our immutable tip as we will never adopt a chain containing them.
135+ --
136+ -- >>> :{
137+ -- weights :: [(Point Blk, PerasWeight)]
138+ -- weights =
139+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
140+ -- , (GenesisPoint, PerasWeight 3)
141+ -- , (BlockPoint 3 "bar", PerasWeight 2)
142+ -- , (BlockPoint 2 "foo", PerasWeight 2)
143+ -- ]
144+ -- :}
145+ --
146+ -- >>> snap = mkPerasWeightSnapshot weights
147+ --
148+ -- >>> prunePerasWeightSnapshot (SlotNo 2) snap
149+ -- [(At (Block {blockPointSlot = SlotNo 2, blockPointHash = "foo"}),PerasWeight 4),(At (Block {blockPointSlot = SlotNo 3, blockPointHash = "bar"}),PerasWeight 2)]
150+ --
151+ -- >>> prunePerasWeightSnapshot (SlotNo 3) snap
152+ -- [(At (Block {blockPointSlot = SlotNo 3, blockPointHash = "bar"}),PerasWeight 2)]
60153prunePerasWeightSnapshot ::
61154 SlotNo ->
62155 PerasWeightSnapshot blk ->
@@ -67,16 +160,73 @@ prunePerasWeightSnapshot slot =
67160 isTooOld :: Point blk -> Bool
68161 isTooOld pt = pointSlot pt < NotOrigin slot
69162
163+ -- | Get the weight boost for a point, or @'mempty' :: 'PerasWeight'@ otherwise.
164+ --
165+ -- >>> :{
166+ -- weights :: [(Point Blk, PerasWeight)]
167+ -- weights =
168+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
169+ -- , (GenesisPoint, PerasWeight 3)
170+ -- , (BlockPoint 3 "bar", PerasWeight 2)
171+ -- , (BlockPoint 2 "foo", PerasWeight 2)
172+ -- ]
173+ -- :}
174+ --
175+ -- >>> snap = mkPerasWeightSnapshot weights
176+ --
177+ -- >>> weightBoostOfPoint snap (BlockPoint 2 "foo")
178+ -- PerasWeight 4
179+ --
180+ -- >>> weightBoostOfPoint snap (BlockPoint 2 "baz")
181+ -- PerasWeight 0
70182weightBoostOfPoint ::
71183 forall blk .
72184 StandardHash blk =>
73185 PerasWeightSnapshot blk -> Point blk -> PerasWeight
74186weightBoostOfPoint (PerasWeightSnapshot weightByPoint) pt =
75187 Map. findWithDefault mempty pt weightByPoint
76188
189+ -- | Get the weight boost for a fragment, ie the sum of all
190+ -- 'weightBoostOfPoint' for all points on the fragment (excluding the anchor).
191+ --
192+ -- Note that this quantity is relative to the anchor of the fragment, so it
193+ -- should only be compared against other fragments with the same anchor.
194+ --
195+ -- >>> :{
196+ -- weights :: [(Point Blk, PerasWeight)]
197+ -- weights =
198+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
199+ -- , (GenesisPoint, PerasWeight 3)
200+ -- , (BlockPoint 3 "bar", PerasWeight 2)
201+ -- , (BlockPoint 2 "foo", PerasWeight 2)
202+ -- ]
203+ -- :}
204+ --
205+ -- >>> :{
206+ -- snap = mkPerasWeightSnapshot weights
207+ -- foo = HeaderFields (SlotNo 2) (BlockNo 1) "foo"
208+ -- bar = HeaderFields (SlotNo 3) (BlockNo 2) "bar"
209+ -- frag0 :: AnchoredFragment (HeaderFields Blk)
210+ -- frag0 = Empty AnchorGenesis :> foo :> bar
211+ -- :}
212+ --
213+ -- >>> weightBoostOfFragment snap frag0
214+ -- PerasWeight 6
215+ --
216+ -- Only keeping the last block from @frag0@:
217+ --
218+ -- >>> frag1 = AF.anchorNewest 1 frag0
219+ -- >>> weightBoostOfFragment snap frag1
220+ -- PerasWeight 2
221+ --
222+ -- Dropping the head from @frag0@, and instead adding an unboosted point:
223+ --
224+ -- >>> frag2 = AF.dropNewest 1 frag0 :> HeaderFields (SlotNo 4) (BlockNo 2) "baz"
225+ -- >>> weightBoostOfFragment snap frag2
226+ -- PerasWeight 4
77227weightBoostOfFragment ::
78228 forall blk h .
79- (HasHeader blk , HasHeader h , HeaderHash blk ~ HeaderHash h ) =>
229+ (StandardHash blk , HasHeader h , HeaderHash blk ~ HeaderHash h ) =>
80230 PerasWeightSnapshot blk ->
81231 AnchoredFragment h ->
82232 PerasWeight
@@ -85,3 +235,12 @@ weightBoostOfFragment weightSnap frag =
85235 foldMap
86236 (weightBoostOfPoint weightSnap . castPoint . blockPoint)
87237 (AF. toOldestFirst frag)
238+
239+ -- $setup
240+ -- >>> import Ouroboros.Consensus.Block
241+ -- >>> import Ouroboros.Network.AnchoredFragment (AnchoredFragment, AnchoredSeq(..), Anchor(..))
242+ -- >>> import qualified Ouroboros.Network.AnchoredFragment as AF
243+ -- >>> :set -XTypeFamilies
244+ -- >>> data Blk = Blk
245+ -- >>> type instance HeaderHash Blk = String
246+ -- >>> instance StandardHash Blk
0 commit comments