Skip to content

Commit 9f7c1be

Browse files
committed
O.C.Peras.Weight: add totalWeightForFragment/takeVolatileSuffix
1 parent 8c6b399 commit 9f7c1be

File tree

1 file changed

+133
-1
lines changed
  • ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Peras

1 file changed

+133
-1
lines changed

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Peras/Weight.hs

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@ module Ouroboros.Consensus.Peras.Weight
2626
-- * Query
2727
, weightBoostOfPoint
2828
, weightBoostOfFragment
29+
, totalWeightOfFragment
30+
, takeVolatileSuffix
2931
) where
3032

3133
import Data.Foldable as Foldable (foldl')
3234
import Data.Map.Strict (Map)
3335
import qualified Data.Map.Strict as Map
36+
import Data.Word (Word64)
3437
import GHC.Generics (Generic)
3538
import NoThunks.Class
3639
import Ouroboros.Consensus.Block
40+
import Ouroboros.Consensus.Config.SecurityParam
3741
import Ouroboros.Network.AnchoredFragment (AnchoredFragment)
3842
import qualified Ouroboros.Network.AnchoredFragment as AF
3943

@@ -236,11 +240,139 @@ weightBoostOfFragment weightSnap frag =
236240
(weightBoostOfPoint weightSnap . castPoint . blockPoint)
237241
(AF.toOldestFirst frag)
238242

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+
239369
-- $setup
370+
-- >>> import Cardano.Ledger.BaseTypes
240371
-- >>> import Ouroboros.Consensus.Block
372+
-- >>> import Ouroboros.Consensus.Config.SecurityParam
241373
-- >>> import Ouroboros.Network.AnchoredFragment (AnchoredFragment, AnchoredSeq(..), Anchor(..))
242374
-- >>> import qualified Ouroboros.Network.AnchoredFragment as AF
243-
-- >>> :set -XTypeFamilies
375+
-- >>> :set -XDataKinds -XTypeApplications -XTypeFamilies
244376
-- >>> data Blk = Blk
245377
-- >>> type instance HeaderHash Blk = String
246378
-- >>> instance StandardHash Blk

0 commit comments

Comments
 (0)