@@ -25,6 +25,7 @@ module Test.Ouroboros.Storage.ChainDB.Model
25
25
, addBlock
26
26
, addBlockPromise
27
27
, addBlocks
28
+ , addPerasCert
28
29
, empty
29
30
30
31
-- * Queries
@@ -44,7 +45,7 @@ module Test.Ouroboros.Storage.ChainDB.Model
44
45
, invalid
45
46
, isOpen
46
47
, isValid
47
- , lastK
48
+ , maxPerasRoundNo
48
49
, tipBlock
49
50
, tipPoint
50
51
, volatileChain
@@ -90,6 +91,7 @@ import Control.Monad.Except (runExcept)
90
91
import Data.Bifunctor (first )
91
92
import qualified Data.ByteString.Lazy as Lazy
92
93
import Data.Containers.ListUtils (nubOrdOn )
94
+ import Data.Foldable (foldMap' )
93
95
import Data.Function (on , (&) )
94
96
import Data.Functor (($>) , (<&>) )
95
97
import Data.List (isInfixOf , isPrefixOf , sortBy )
@@ -100,7 +102,6 @@ import Data.Proxy
100
102
import Data.Set (Set )
101
103
import qualified Data.Set as Set
102
104
import Data.TreeDiff
103
- import Data.Word (Word64 )
104
105
import GHC.Generics (Generic )
105
106
import Ouroboros.Consensus.Block
106
107
import Ouroboros.Consensus.Config
@@ -147,6 +148,7 @@ data Model blk = Model
147
148
-- ^ The VolatileDB
148
149
, immutableDbChain :: Chain blk
149
150
-- ^ The ImmutableDB
151
+ , perasCerts :: Map PerasRoundNo (PerasCert blk )
150
152
, cps :: CPS. ChainProducerState blk
151
153
, currentLedger :: ExtLedgerState blk EmptyMK
152
154
, initLedger :: ExtLedgerState blk EmptyMK
@@ -233,72 +235,78 @@ tipPoint = maybe GenesisPoint blockPoint . tipBlock
233
235
getMaxSlotNo :: HasHeader blk => Model blk -> MaxSlotNo
234
236
getMaxSlotNo = foldMap (MaxSlotNo . blockSlot) . blocks
235
237
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:
251
240
--
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
257
246
maxActualRollback k m =
258
- fromIntegral
259
- . length
247
+ foldMap' (weightBoostOfPoint weights)
260
248
. takeWhile (/= immutableTipPoint)
261
249
. map blockPoint
262
250
. Chain. toNewestFirst
263
251
. currentChain
264
252
$ m
265
253
where
254
+ weights = perasWeights m
255
+
266
256
immutableTipPoint = Chain. headPoint (immutableChain k m)
267
257
268
258
-- | Return the immutable prefix of the current chain.
269
259
--
270
260
-- This is the longest of the given two chains:
271
261
--
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.
273
263
-- 2. The chain formed by the blocks in 'immutableDbChain', i.e., the
274
264
-- \"ImmutableDB\". We need to take this case in consideration because the
275
265
-- VolatileDB might have been wiped.
276
266
--
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
278
268
-- the background thread copying blocks from the VolatileDB to the ImmutableDB
279
269
-- might not have caught up yet. This means we cannot use the tip of the
280
270
-- ImmutableDB to know the most recent \"immutable\" block.
281
271
immutableChain ::
272
+ forall blk .
273
+ HasHeader blk =>
282
274
SecurityParam ->
283
275
Model blk ->
284
276
Chain blk
285
- immutableChain ( SecurityParam k) m =
277
+ immutableChain k m =
286
278
maxBy
279
+ -- As one of the two chains is a prefix of the other, Peras weight doesn't
280
+ -- matter here.
287
281
Chain. length
288
- (Chain. drop ( fromIntegral $ unNonZero k) (currentChain m))
282
+ (dropAtMostWeight (maxRollbackWeight k) (currentChain m))
289
283
(immutableDbChain m)
290
284
where
291
285
maxBy f a b
292
286
| f a >= f b = a
293
287
| otherwise = b
294
288
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
+
295
303
-- | Return the volatile suffix of the current chain.
296
304
--
297
305
-- The opposite of 'immutableChain'.
298
306
--
299
307
-- This is the shortest of the given two chain fragments:
300
308
--
301
- -- 1. The last @k@ blocks of the current chain.
309
+ -- 1. The longest suffix of the current chain with weight at most @k@ .
302
310
-- 2. The suffix of the current chain not part of the 'immutableDbChain', i.e.,
303
311
-- the \"ImmutableDB\".
304
312
volatileChain ::
@@ -370,6 +378,17 @@ isValid = flip getIsValid
370
378
getLoEFragment :: Model blk -> LoE (AnchoredFragment blk )
371
379
getLoEFragment = loeFragment
372
380
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
+
373
392
{- ------------------------------------------------------------------------------
374
393
Construction
375
394
-------------------------------------------------------------------------------}
@@ -383,6 +402,7 @@ empty loe initLedger =
383
402
Model
384
403
{ volatileDbBlocks = Map. empty
385
404
, immutableDbChain = Chain. Genesis
405
+ , perasCerts = Map. empty
386
406
, cps = CPS. initChainProducerState Chain. Genesis
387
407
, currentLedger = initLedger
388
408
, initLedger = initLedger
@@ -422,6 +442,23 @@ addBlock cfg blk m
422
442
-- If it's an invalid block we've seen before, ignore it.
423
443
Map. member (blockHash blk) (invalid m)
424
444
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
+
425
462
chainSelection ::
426
463
forall blk .
427
464
( LedgerTablesAreTrivial (ExtLedgerState blk )
@@ -434,6 +471,7 @@ chainSelection cfg m =
434
471
Model
435
472
{ volatileDbBlocks = volatileDbBlocks m
436
473
, immutableDbChain = immutableDbChain m
474
+ , perasCerts = perasCerts m
437
475
, cps = CPS. switchFork newChain (cps m)
438
476
, currentLedger = newLedger
439
477
, initLedger = initLedger m
@@ -533,15 +571,12 @@ chainSelection cfg m =
533
571
. selectChain
534
572
(Proxy @ (BlockProtocol blk ))
535
573
(projectChainOrderConfig (configBlock cfg))
536
- ( weightedSelectView (configBlock cfg) weights
574
+ ( weightedSelectView (configBlock cfg) (perasWeights m)
537
575
. Chain. toAnchoredFragment
538
576
. fmap getHeader
539
577
)
540
578
(currentChain m)
541
579
$ consideredCandidates
542
- where
543
- -- TODO enrich with Peras weights/certs
544
- weights = emptyPerasWeightSnapshot
545
580
546
581
-- We update the set of valid blocks with all valid blocks on all candidate
547
582
-- chains that are considered by the modeled chain selection. This ensures
@@ -871,12 +906,9 @@ validChains cfg m bs =
871
906
sortChains =
872
907
sortBy $
873
908
flip
874
- ( Fragment. compareAnchoredFragments (configBlock cfg) weights
909
+ ( Fragment. compareAnchoredFragments (configBlock cfg) (perasWeights m)
875
910
`on` (Chain. toAnchoredFragment . fmap getHeader)
876
911
)
877
- where
878
- -- TODO enrich with Peras weights/certs
879
- weights = emptyPerasWeightSnapshot
880
912
881
913
classify ::
882
914
ValidatedChain blk ->
@@ -910,7 +942,11 @@ between k from to m = do
910
942
fork <- errFork
911
943
-- See #871.
912
944
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
914
950
then return $ Fragment. toOldestFirst fork
915
951
-- We cannot stream from an old fork
916
952
else Left $ ForkTooOld from
@@ -1050,6 +1086,7 @@ garbageCollect ::
1050
1086
garbageCollect secParam m@ Model {.. } =
1051
1087
m
1052
1088
{ volatileDbBlocks = Map. filter (not . collectable) volatileDbBlocks
1089
+ -- TODO garbage collection Peras certs?
1053
1090
}
1054
1091
where
1055
1092
-- TODO what about iterators that will stream garbage collected blocks?
@@ -1101,6 +1138,14 @@ wipeVolatileDB cfg m =
1101
1138
m' =
1102
1139
(closeDB m)
1103
1140
{ 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
1104
1149
, cps = CPS. switchFork newChain (cps m)
1105
1150
, currentLedger = newLedger
1106
1151
, invalid = Map. empty
0 commit comments