@@ -25,6 +25,7 @@ module Test.Ouroboros.Storage.ChainDB.Model
2525 , addBlock
2626 , addBlockPromise
2727 , addBlocks
28+ , addPerasCert
2829 , empty
2930
3031 -- * Queries
@@ -44,7 +45,7 @@ module Test.Ouroboros.Storage.ChainDB.Model
4445 , invalid
4546 , isOpen
4647 , isValid
47- , lastK
48+ , maxPerasRoundNo
4849 , tipBlock
4950 , tipPoint
5051 , volatileChain
@@ -90,6 +91,7 @@ import Control.Monad.Except (runExcept)
9091import Data.Bifunctor (first )
9192import qualified Data.ByteString.Lazy as Lazy
9293import Data.Containers.ListUtils (nubOrdOn )
94+ import Data.Foldable (foldMap' )
9395import Data.Function (on , (&) )
9496import Data.Functor (($>) , (<&>) )
9597import Data.List (isInfixOf , isPrefixOf , sortBy )
@@ -100,7 +102,6 @@ import Data.Proxy
100102import Data.Set (Set )
101103import qualified Data.Set as Set
102104import Data.TreeDiff
103- import Data.Word (Word64 )
104105import GHC.Generics (Generic )
105106import Ouroboros.Consensus.Block
106107import Ouroboros.Consensus.Config
@@ -147,6 +148,7 @@ data Model blk = Model
147148 -- ^ The VolatileDB
148149 , immutableDbChain :: Chain blk
149150 -- ^ The ImmutableDB
151+ , perasCerts :: Map PerasRoundNo (PerasCert blk )
150152 , cps :: CPS. ChainProducerState blk
151153 , currentLedger :: ExtLedgerState blk EmptyMK
152154 , initLedger :: ExtLedgerState blk EmptyMK
@@ -233,72 +235,78 @@ tipPoint = maybe GenesisPoint blockPoint . tipBlock
233235getMaxSlotNo :: HasHeader blk => Model blk -> MaxSlotNo
234236getMaxSlotNo = foldMap (MaxSlotNo . blockSlot) . blocks
235237
236- lastK ::
237- HasHeader a =>
238- SecurityParam ->
239- -- | Provided since `AnchoredFragment` is not a functor
240- (blk -> a ) ->
241- Model blk ->
242- AnchoredFragment a
243- lastK (SecurityParam k) f =
244- Fragment. anchorNewest (unNonZero k)
245- . Chain. toAnchoredFragment
246- . fmap f
247- . currentChain
248-
249- -- | Actual number of blocks that can be rolled back. Equal to @k@, except
250- -- when:
238+ -- | Actual amount of weight that can be rolled back. This can non-trivially
239+ -- smaller than @k@ in the following cases:
251240--
252- -- * Near genesis, the chain might not be @k@ blocks long yet.
253- -- * After VolatileDB corruption, the whole chain might be >= @k@ blocks, but
254- -- the tip of the ImmutableDB might be closer than @k@ blocks away from the
255- -- current chain's tip .
256- maxActualRollback :: HasHeader blk => SecurityParam -> Model blk -> Word64
241+ -- * Near genesis, the chain might not have grown sufficiently yet.
242+ -- * After VolatileDB corruption, the whole chain might have more than weight
243+ -- @k@, but the tip of the ImmutableDB might be buried under significantly
244+ -- less than weight @k@ worth of blocks .
245+ maxActualRollback :: HasHeader blk => SecurityParam -> Model blk -> PerasWeight
257246maxActualRollback k m =
258- fromIntegral
259- . length
247+ foldMap' (weightBoostOfPoint weights)
260248 . takeWhile (/= immutableTipPoint)
261249 . map blockPoint
262250 . Chain. toNewestFirst
263251 . currentChain
264252 $ m
265253 where
254+ weights = perasWeights m
255+
266256 immutableTipPoint = Chain. headPoint (immutableChain k m)
267257
268258-- | Return the immutable prefix of the current chain.
269259--
270260-- This is the longest of the given two chains:
271261--
272- -- 1. The current chain with the last @k@ blocks dropped.
262+ -- 1. The current chain with the longest suffix of weight at most @k@ dropped.
273263-- 2. The chain formed by the blocks in 'immutableDbChain', i.e., the
274264-- \"ImmutableDB\". We need to take this case in consideration because the
275265-- VolatileDB might have been wiped.
276266--
277- -- We need this because we do not allow rolling back more than @k@ blocks , but
267+ -- We need this because we do not allow rolling back more than weight @k@, but
278268-- the background thread copying blocks from the VolatileDB to the ImmutableDB
279269-- might not have caught up yet. This means we cannot use the tip of the
280270-- ImmutableDB to know the most recent \"immutable\" block.
281271immutableChain ::
272+ forall blk .
273+ HasHeader blk =>
282274 SecurityParam ->
283275 Model blk ->
284276 Chain blk
285- immutableChain ( SecurityParam k) m =
277+ immutableChain k m =
286278 maxBy
279+ -- As one of the two chains is a prefix of the other, Peras weight doesn't
280+ -- matter here.
287281 Chain. length
288- (Chain. drop ( fromIntegral $ unNonZero k) (currentChain m))
282+ (dropAtMostWeight (maxRollbackWeight k) (currentChain m))
289283 (immutableDbChain m)
290284 where
291285 maxBy f a b
292286 | f a >= f b = a
293287 | otherwise = b
294288
289+ weights = perasWeights m
290+
291+ -- Drop the longest suffix with at most the given weight.
292+ dropAtMostWeight :: PerasWeight -> Chain blk -> Chain blk
293+ dropAtMostWeight budget = go mempty
294+ where
295+ go w = \ case
296+ Genesis -> Genesis
297+ c@ (c' :> b)
298+ | w' <= budget -> go w' c'
299+ | otherwise -> c
300+ where
301+ w' = w <> PerasWeight 1 <> weightBoostOfPoint weights (blockPoint b)
302+
295303-- | Return the volatile suffix of the current chain.
296304--
297305-- The opposite of 'immutableChain'.
298306--
299307-- This is the shortest of the given two chain fragments:
300308--
301- -- 1. The last @k@ blocks of the current chain.
309+ -- 1. The longest suffix of the current chain with weight at most @k@ .
302310-- 2. The suffix of the current chain not part of the 'immutableDbChain', i.e.,
303311-- the \"ImmutableDB\".
304312volatileChain ::
@@ -370,6 +378,17 @@ isValid = flip getIsValid
370378getLoEFragment :: Model blk -> LoE (AnchoredFragment blk )
371379getLoEFragment = loeFragment
372380
381+ perasWeights :: StandardHash blk => Model blk -> PerasWeightSnapshot blk
382+ perasWeights =
383+ mkPerasWeightSnapshot
384+ -- TODO make boost per cert configurable
385+ . fmap (\ c -> (perasCertBoostedBlock c, boostPerCert))
386+ . Map. elems
387+ . perasCerts
388+
389+ maxPerasRoundNo :: Model blk -> Maybe PerasRoundNo
390+ maxPerasRoundNo m = fst <$> Map. lookupMax (perasCerts m)
391+
373392{- ------------------------------------------------------------------------------
374393 Construction
375394-------------------------------------------------------------------------------}
@@ -383,6 +402,7 @@ empty loe initLedger =
383402 Model
384403 { volatileDbBlocks = Map. empty
385404 , immutableDbChain = Chain. Genesis
405+ , perasCerts = Map. empty
386406 , cps = CPS. initChainProducerState Chain. Genesis
387407 , currentLedger = initLedger
388408 , initLedger = initLedger
@@ -422,6 +442,23 @@ addBlock cfg blk m
422442 -- If it's an invalid block we've seen before, ignore it.
423443 Map. member (blockHash blk) (invalid m)
424444
445+ addPerasCert ::
446+ forall blk .
447+ (LedgerSupportsProtocol blk , LedgerTablesAreTrivial (ExtLedgerState blk )) =>
448+ TopLevelConfig blk ->
449+ PerasCert blk ->
450+ Model blk ->
451+ Model blk
452+ addPerasCert cfg cert m
453+ -- Do not alter the model when a certificate for that round already exists.
454+ | Map. member certRound (perasCerts m) = m
455+ | otherwise =
456+ chainSelection
457+ cfg
458+ m{perasCerts = Map. insert certRound cert (perasCerts m)}
459+ where
460+ certRound = perasCertRound cert
461+
425462chainSelection ::
426463 forall blk .
427464 ( LedgerTablesAreTrivial (ExtLedgerState blk )
@@ -434,6 +471,7 @@ chainSelection cfg m =
434471 Model
435472 { volatileDbBlocks = volatileDbBlocks m
436473 , immutableDbChain = immutableDbChain m
474+ , perasCerts = perasCerts m
437475 , cps = CPS. switchFork newChain (cps m)
438476 , currentLedger = newLedger
439477 , initLedger = initLedger m
@@ -533,15 +571,12 @@ chainSelection cfg m =
533571 . selectChain
534572 (Proxy @ (BlockProtocol blk ))
535573 (projectChainOrderConfig (configBlock cfg))
536- ( weightedSelectView (configBlock cfg) weights
574+ ( weightedSelectView (configBlock cfg) (perasWeights m)
537575 . Chain. toAnchoredFragment
538576 . fmap getHeader
539577 )
540578 (currentChain m)
541579 $ consideredCandidates
542- where
543- -- TODO enrich with Peras weights/certs
544- weights = emptyPerasWeightSnapshot
545580
546581 -- We update the set of valid blocks with all valid blocks on all candidate
547582 -- chains that are considered by the modeled chain selection. This ensures
@@ -871,12 +906,9 @@ validChains cfg m bs =
871906 sortChains =
872907 sortBy $
873908 flip
874- ( Fragment. compareAnchoredFragments (configBlock cfg) weights
909+ ( Fragment. compareAnchoredFragments (configBlock cfg) (perasWeights m)
875910 `on` (Chain. toAnchoredFragment . fmap getHeader)
876911 )
877- where
878- -- TODO enrich with Peras weights/certs
879- weights = emptyPerasWeightSnapshot
880912
881913 classify ::
882914 ValidatedChain blk ->
@@ -910,7 +942,11 @@ between k from to m = do
910942 fork <- errFork
911943 -- See #871.
912944 if partOfCurrentChain fork
913- || Fragment. forksAtMostKBlocks (maxActualRollback k m) currentFrag fork
945+ || Fragment. forksAtMostKWeight
946+ (perasWeights m)
947+ (maxActualRollback k m)
948+ currentFrag
949+ fork
914950 then return $ Fragment. toOldestFirst fork
915951 -- We cannot stream from an old fork
916952 else Left $ ForkTooOld from
@@ -1050,6 +1086,7 @@ garbageCollect ::
10501086garbageCollect secParam m@ Model {.. } =
10511087 m
10521088 { volatileDbBlocks = Map. filter (not . collectable) volatileDbBlocks
1089+ -- TODO garbage collection Peras certs?
10531090 }
10541091 where
10551092 -- TODO what about iterators that will stream garbage collected blocks?
@@ -1101,6 +1138,14 @@ wipeVolatileDB cfg m =
11011138 m' =
11021139 (closeDB m)
11031140 { volatileDbBlocks = Map. empty
1141+ , -- TODO: Currently, the SUT has no persistence of Peras certs across
1142+ -- restarts, but this will change. There are at least two options:
1143+ --
1144+ -- * Change this command to mean "wipe volatile state" (including
1145+ -- volatile certificates)
1146+ --
1147+ -- * Add a separate "Wipe volatile certs".
1148+ perasCerts = Map. empty
11041149 , cps = CPS. switchFork newChain (cps m)
11051150 , currentLedger = newLedger
11061151 , invalid = Map. empty
0 commit comments