@@ -26,14 +26,18 @@ module Ouroboros.Consensus.Peras.Weight
26
26
-- * Query
27
27
, weightBoostOfPoint
28
28
, weightBoostOfFragment
29
+ , totalWeightOfFragment
30
+ , takeVolatileSuffix
29
31
) where
30
32
31
33
import Data.Foldable as Foldable (foldl' )
32
34
import Data.Map.Strict (Map )
33
35
import qualified Data.Map.Strict as Map
36
+ import Data.Word (Word64 )
34
37
import GHC.Generics (Generic )
35
38
import NoThunks.Class
36
39
import Ouroboros.Consensus.Block
40
+ import Ouroboros.Consensus.Config.SecurityParam
37
41
import Ouroboros.Network.AnchoredFragment (AnchoredFragment )
38
42
import qualified Ouroboros.Network.AnchoredFragment as AF
39
43
@@ -236,11 +240,139 @@ weightBoostOfFragment weightSnap frag =
236
240
(weightBoostOfPoint weightSnap . castPoint . blockPoint)
237
241
(AF. toOldestFirst frag)
238
242
243
+ -- | Get the total weight for a fragment, ie the length plus the weight boost
244
+ -- ('weightBoostOfFragment') of the fragment.
245
+ --
246
+ -- Note that this quantity is relative to the anchor of the fragment, so it
247
+ -- should only be compared against other fragments with the same anchor.
248
+ --
249
+ -- >>> :{
250
+ -- weights :: [(Point Blk, PerasWeight)]
251
+ -- weights =
252
+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
253
+ -- , (GenesisPoint, PerasWeight 3)
254
+ -- , (BlockPoint 3 "bar", PerasWeight 2)
255
+ -- , (BlockPoint 2 "foo", PerasWeight 2)
256
+ -- ]
257
+ -- :}
258
+ --
259
+ -- >>> :{
260
+ -- snap = mkPerasWeightSnapshot weights
261
+ -- foo = HeaderFields (SlotNo 2) (BlockNo 1) "foo"
262
+ -- bar = HeaderFields (SlotNo 3) (BlockNo 2) "bar"
263
+ -- frag0 :: AnchoredFragment (HeaderFields Blk)
264
+ -- frag0 = Empty AnchorGenesis :> foo :> bar
265
+ -- :}
266
+ --
267
+ -- >>> totalWeightOfFragment snap frag0
268
+ -- PerasWeight 8
269
+ --
270
+ -- Only keeping the last block from @frag0@:
271
+ --
272
+ -- >>> frag1 = AF.anchorNewest 1 frag0
273
+ -- >>> totalWeightOfFragment snap frag1
274
+ -- PerasWeight 3
275
+ --
276
+ -- Dropping the head from @frag0@, and instead adding an unboosted point:
277
+ --
278
+ -- >>> frag2 = AF.dropNewest 1 frag0 :> HeaderFields (SlotNo 4) (BlockNo 2) "baz"
279
+ -- >>> totalWeightOfFragment snap frag2
280
+ -- PerasWeight 6
281
+ totalWeightOfFragment ::
282
+ forall blk h .
283
+ (StandardHash blk , HasHeader h , HeaderHash blk ~ HeaderHash h ) =>
284
+ PerasWeightSnapshot blk ->
285
+ AnchoredFragment h ->
286
+ PerasWeight
287
+ totalWeightOfFragment weightSnap frag =
288
+ weightLength <> weightBoost
289
+ where
290
+ weightLength = PerasWeight $ fromIntegral $ AF. length frag
291
+ weightBoost = weightBoostOfFragment weightSnap frag
292
+
293
+ -- | Take the longest suffix of the given fragment with total weight
294
+ -- ('totalWeightOfFragment') at most @k@. This is the volatile suffix of blocks
295
+ -- which are subject to rollback.
296
+ --
297
+ -- If the total weight of the fragment is at least @k@, then the anchor of the
298
+ -- output fragment is the most recent point on the input fragment that is buried
299
+ -- under at least weight @k@ (also counting the weight boost of that point).
300
+ --
301
+ -- >>> :{
302
+ -- weights :: [(Point Blk, PerasWeight)]
303
+ -- weights =
304
+ -- [ (BlockPoint 2 "foo", PerasWeight 2)
305
+ -- , (GenesisPoint, PerasWeight 3)
306
+ -- , (BlockPoint 3 "bar", PerasWeight 2)
307
+ -- , (BlockPoint 2 "foo", PerasWeight 2)
308
+ -- ]
309
+ -- snap = mkPerasWeightSnapshot weights
310
+ -- foo = HeaderFields (SlotNo 2) (BlockNo 1) "foo"
311
+ -- bar = HeaderFields (SlotNo 3) (BlockNo 2) "bar"
312
+ -- frag :: AnchoredFragment (HeaderFields Blk)
313
+ -- frag = Empty AnchorGenesis :> foo :> bar
314
+ -- :}
315
+ --
316
+ -- >>> k1 = SecurityParam $ knownNonZeroBounded @1
317
+ -- >>> k3 = SecurityParam $ knownNonZeroBounded @3
318
+ -- >>> k6 = SecurityParam $ knownNonZeroBounded @6
319
+ -- >>> k9 = SecurityParam $ knownNonZeroBounded @9
320
+ --
321
+ -- >>> AF.toOldestFirst $ takeVolatileSuffix snap k1 frag
322
+ -- []
323
+ --
324
+ -- >>> AF.toOldestFirst $ takeVolatileSuffix snap k3 frag
325
+ -- [HeaderFields {headerFieldSlot = SlotNo 3, headerFieldBlockNo = BlockNo 2, headerFieldHash = "bar"}]
326
+ --
327
+ -- >>> AF.toOldestFirst $ takeVolatileSuffix snap k6 frag
328
+ -- [HeaderFields {headerFieldSlot = SlotNo 3, headerFieldBlockNo = BlockNo 2, headerFieldHash = "bar"}]
329
+ --
330
+ -- >>> AF.toOldestFirst $ takeVolatileSuffix snap k9 frag
331
+ -- [HeaderFields {headerFieldSlot = SlotNo 2, headerFieldBlockNo = BlockNo 1, headerFieldHash = "foo"},HeaderFields {headerFieldSlot = SlotNo 3, headerFieldBlockNo = BlockNo 2, headerFieldHash = "bar"}]
332
+ takeVolatileSuffix ::
333
+ forall blk h .
334
+ (StandardHash blk , HasHeader h , HeaderHash blk ~ HeaderHash h ) =>
335
+ PerasWeightSnapshot blk ->
336
+ -- | The security parameter @k@ is interpreted as a weight.
337
+ SecurityParam ->
338
+ AnchoredFragment h ->
339
+ AnchoredFragment h
340
+ takeVolatileSuffix snap secParam frag
341
+ | Map. null $ getPerasWeightSnapshot snap =
342
+ -- Optimize the case where Peras is disabled.
343
+ AF. anchorNewest (unPerasWeight k) frag
344
+ | hasAtMostWeightK frag = frag
345
+ | otherwise = go 0 lenFrag (AF. Empty $ AF. headAnchor frag)
346
+ where
347
+ k :: PerasWeight
348
+ k = maxRollbackWeight secParam
349
+
350
+ hasAtMostWeightK :: AnchoredFragment h -> Bool
351
+ hasAtMostWeightK f = totalWeightOfFragment snap f <= k
352
+
353
+ lenFrag = fromIntegral $ AF. length frag
354
+
355
+ -- Binary search for the longest suffix of @frag@ which 'hasAtMostWeightK'.
356
+ go ::
357
+ Word64 -> -- lb. The length lb suffix satisfies 'hasAtMostWeightK'.
358
+ Word64 -> -- ub. The length ub suffix does not satisfy 'hasAtMostWeightK'.
359
+ AnchoredFragment h -> -- The length lb suffix.
360
+ AnchoredFragment h
361
+ go lb ub lbFrag
362
+ | lb + 1 == ub = lbFrag
363
+ | hasAtMostWeightK midFrag = go mid ub midFrag
364
+ | otherwise = go lb mid lbFrag
365
+ where
366
+ mid = (lb + ub) `div` 2
367
+ midFrag = AF. anchorNewest mid frag
368
+
239
369
-- $setup
370
+ -- >>> import Cardano.Ledger.BaseTypes
240
371
-- >>> import Ouroboros.Consensus.Block
372
+ -- >>> import Ouroboros.Consensus.Config.SecurityParam
241
373
-- >>> import Ouroboros.Network.AnchoredFragment (AnchoredFragment, AnchoredSeq(..), Anchor(..))
242
374
-- >>> import qualified Ouroboros.Network.AnchoredFragment as AF
243
- -- >>> :set -XTypeFamilies
375
+ -- >>> :set -XDataKinds -XTypeApplications - XTypeFamilies
244
376
-- >>> data Blk = Blk
245
377
-- >>> type instance HeaderHash Blk = String
246
378
-- >>> instance StandardHash Blk
0 commit comments