5
5
{-# LANGUAGE ScopedTypeVariables #-}
6
6
{-# LANGUAGE TypeOperators #-}
7
7
8
+ -- | Data structure for tracking the weight of blocks due to Peras boosts.
8
9
module Ouroboros.Consensus.Peras.Weight
9
- ( -- * 'PerasWeightSnapshot'
10
+ ( -- * 'PerasWeightSnapshot' type
10
11
PerasWeightSnapshot
12
+
13
+ -- * Construction
11
14
, emptyPerasWeightSnapshot
12
15
, mkPerasWeightSnapshot
16
+
17
+ -- * Conversion
13
18
, perasWeightSnapshotToList
19
+
20
+ -- * Insertion
14
21
, addToPerasWeightSnapshot
22
+
23
+ -- * Pruning
15
24
, prunePerasWeightSnapshot
25
+
26
+ -- * Query
16
27
, weightBoostOfPoint
17
28
, weightBoostOfFragment
18
29
) where
@@ -26,16 +37,37 @@ import Ouroboros.Consensus.Block
26
37
import Ouroboros.Network.AnchoredFragment (AnchoredFragment )
27
38
import qualified Ouroboros.Network.AnchoredFragment as AF
28
39
40
+ -- | Data structure for tracking the weight of blocks due to Peras boosts.
29
41
newtype PerasWeightSnapshot blk = PerasWeightSnapshot
30
42
{ getPerasWeightSnapshot :: Map (Point blk ) PerasWeight
31
43
}
32
- deriving stock ( Show , Eq )
44
+ deriving stock Eq
33
45
deriving Generic
34
46
deriving newtype NoThunks
35
47
48
+ instance StandardHash blk => Show (PerasWeightSnapshot blk ) where
49
+ show = show . perasWeightSnapshotToList
50
+
51
+ -- | An empty 'PerasWeightSnapshot' not containing any boosted blocks.
36
52
emptyPerasWeightSnapshot :: PerasWeightSnapshot blk
37
53
emptyPerasWeightSnapshot = PerasWeightSnapshot Map. empty
38
54
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)]
39
71
mkPerasWeightSnapshot ::
40
72
StandardHash blk =>
41
73
[(Point blk , PerasWeight )] ->
@@ -45,9 +77,47 @@ mkPerasWeightSnapshot =
45
77
(\ s (pt, weight) -> addToPerasWeightSnapshot pt weight s)
46
78
emptyPerasWeightSnapshot
47
79
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)]
48
96
perasWeightSnapshotToList :: PerasWeightSnapshot blk -> [(Point blk , PerasWeight )]
49
- perasWeightSnapshotToList = Map. toList . getPerasWeightSnapshot
97
+ perasWeightSnapshotToList = Map. toAscList . getPerasWeightSnapshot
50
98
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)]
51
121
addToPerasWeightSnapshot ::
52
122
StandardHash blk =>
53
123
Point blk ->
@@ -57,6 +127,29 @@ addToPerasWeightSnapshot ::
57
127
addToPerasWeightSnapshot pt weight =
58
128
PerasWeightSnapshot . Map. insertWith (<>) pt weight . getPerasWeightSnapshot
59
129
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)]
60
153
prunePerasWeightSnapshot ::
61
154
SlotNo ->
62
155
PerasWeightSnapshot blk ->
@@ -67,16 +160,73 @@ prunePerasWeightSnapshot slot =
67
160
isTooOld :: Point blk -> Bool
68
161
isTooOld pt = pointSlot pt < NotOrigin slot
69
162
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
70
182
weightBoostOfPoint ::
71
183
forall blk .
72
184
StandardHash blk =>
73
185
PerasWeightSnapshot blk -> Point blk -> PerasWeight
74
186
weightBoostOfPoint (PerasWeightSnapshot weightByPoint) pt =
75
187
Map. findWithDefault mempty pt weightByPoint
76
188
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
77
227
weightBoostOfFragment ::
78
228
forall blk h .
79
- (HasHeader blk , HasHeader h , HeaderHash blk ~ HeaderHash h ) =>
229
+ (StandardHash blk , HasHeader h , HeaderHash blk ~ HeaderHash h ) =>
80
230
PerasWeightSnapshot blk ->
81
231
AnchoredFragment h ->
82
232
PerasWeight
@@ -85,3 +235,12 @@ weightBoostOfFragment weightSnap frag =
85
235
foldMap
86
236
(weightBoostOfPoint weightSnap . castPoint . blockPoint)
87
237
(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