@@ -70,9 +70,11 @@ module Test.Ouroboros.Storage.ChainDB.Model (
7070 , garbageCollectable
7171 , garbageCollectableIteratorNext
7272 , garbageCollectablePoint
73+ , getFragmentBetween
7374 , immutableDbChain
7475 , initLedger
7576 , reopen
77+ , updateLoE
7678 , validChains
7779 , volatileDbBlocks
7880 , wipeVolatileDB
@@ -81,8 +83,11 @@ module Test.Ouroboros.Storage.ChainDB.Model (
8183import Codec.Serialise (Serialise , serialise )
8284import Control.Monad (unless )
8385import Control.Monad.Except (runExcept )
86+ import Data.Bifunctor (first )
8487import qualified Data.ByteString.Lazy as Lazy
85- import Data.Function (on )
88+ import Data.Containers.ListUtils (nubOrdOn )
89+ import Data.Function (on , (&) )
90+ import Data.Functor (($>) , (<&>) )
8691import Data.List (isInfixOf , isPrefixOf , sortBy )
8792import Data.Map.Strict (Map )
8893import qualified Data.Map.Strict as Map
@@ -104,8 +109,8 @@ import Ouroboros.Consensus.Protocol.MockChainSel
104109import Ouroboros.Consensus.Storage.ChainDB.API (AddBlockPromise (.. ),
105110 AddBlockResult (.. ), BlockComponent (.. ),
106111 ChainDbError (.. ), InvalidBlockReason (.. ),
107- IteratorResult (.. ), StreamFrom (.. ), StreamTo (.. ),
108- UnknownRange (.. ), validBounds )
112+ IteratorResult (.. ), LoE (.. ), StreamFrom (.. ),
113+ StreamTo ( .. ), UnknownRange (.. ), validBounds )
109114import Ouroboros.Consensus.Storage.ChainDB.Impl.ChainSel (olderThanK )
110115import Ouroboros.Consensus.Storage.LedgerDB
111116import Ouroboros.Consensus.Util (repeatedly )
@@ -119,7 +124,7 @@ import qualified Ouroboros.Network.Mock.Chain as Chain
119124import Ouroboros.Network.Mock.ProducerState (ChainProducerState )
120125import qualified Ouroboros.Network.Mock.ProducerState as CPS
121126import Test.Cardano.Slotting.TreeDiff ()
122-
127+ import Test.Util.Orphans.ToExpr ()
123128
124129type IteratorId = Int
125130
@@ -136,6 +141,7 @@ data Model blk = Model {
136141 , valid :: Set (HeaderHash blk )
137142 , invalid :: InvalidBlocks blk
138143 , currentSlot :: SlotNo
144+ , loeFragment :: LoE (AnchoredFragment blk )
139145 , maxClockSkew :: Word64
140146 -- ^ Max clock skew in terms of slots. A static configuration parameter.
141147 , isOpen :: Bool
@@ -364,10 +370,12 @@ getLedgerDB cfg m@Model{..} =
364370-------------------------------------------------------------------------------}
365371
366372empty ::
367- ExtLedgerState blk
373+ HasHeader blk
374+ => LoE ()
375+ -> ExtLedgerState blk
368376 -> Word64 -- ^ Max clock skew in number of blocks
369377 -> Model blk
370- empty initLedger maxClockSkew = Model {
378+ empty loe initLedger maxClockSkew = Model {
371379 volatileDbBlocks = Map. empty
372380 , immutableDbChain = Chain. Genesis
373381 , cps = CPS. initChainProducerState Chain. Genesis
@@ -379,6 +387,7 @@ empty initLedger maxClockSkew = Model {
379387 , currentSlot = 0
380388 , maxClockSkew = maxClockSkew
381389 , isOpen = True
390+ , loeFragment = loe $> Fragment. Empty Fragment. AnchorGenesis
382391 }
383392
384393-- | Advance the 'currentSlot' of the model to the given 'SlotNo' if the
@@ -392,22 +401,13 @@ addBlock :: forall blk. LedgerSupportsProtocol blk
392401 => TopLevelConfig blk
393402 -> blk
394403 -> Model blk -> Model blk
395- addBlock cfg blk m = Model {
396- volatileDbBlocks = volatileDbBlocks'
397- , immutableDbChain = immutableDbChain m
398- , cps = CPS. switchFork newChain (cps m)
399- , currentLedger = newLedger
400- , initLedger = initLedger m
401- , iterators = iterators m
402- , valid = valid'
403- , invalid = invalid'
404- , currentSlot = currentSlot m
405- , maxClockSkew = maxClockSkew m
406- , isOpen = True
407- }
404+ addBlock cfg blk m
405+ | ignoreBlock = m
406+ | otherwise = chainSelection cfg m {
407+ volatileDbBlocks = Map. insert (blockHash blk) blk (volatileDbBlocks m)
408+ }
408409 where
409410 secParam = configSecurityParam cfg
410-
411411 immBlockNo = immutableBlockNo secParam m
412412
413413 hdr = getHeader blk
@@ -419,34 +419,105 @@ addBlock cfg blk m = Model {
419419 -- If it's an invalid block we've seen before, ignore it.
420420 Map. member (blockHash blk) (invalid m)
421421
422- volatileDbBlocks' :: Map (HeaderHash blk ) blk
423- volatileDbBlocks'
424- | ignoreBlock
425- = volatileDbBlocks m
426- | otherwise
427- = Map. insert (blockHash blk) blk (volatileDbBlocks m)
422+ chainSelection :: forall blk . LedgerSupportsProtocol blk
423+ => TopLevelConfig blk
424+ -> Model blk -> Model blk
425+ chainSelection cfg m = Model {
426+ volatileDbBlocks = volatileDbBlocks m
427+ , immutableDbChain = immutableDbChain m
428+ , cps = CPS. switchFork newChain (cps m)
429+ , currentLedger = newLedger
430+ , initLedger = initLedger m
431+ , iterators = iterators m
432+ , valid = valid'
433+ , invalid = invalid'
434+ , currentSlot = currentSlot m
435+ , maxClockSkew = maxClockSkew m
436+ , isOpen = True
437+ , loeFragment = loeFragment m
438+ }
439+ where
440+ secParam = configSecurityParam cfg
428441
429442 -- @invalid'@ will be a (non-strict) superset of the previous value of
430443 -- @invalid@, see 'validChains', thus no need to union.
431444 invalid' :: InvalidBlocks blk
432445 candidates :: [(Chain blk , ExtLedgerState blk )]
433- (invalid', candidates) =
434- validChains cfg m (immutableDbBlocks m <> volatileDbBlocks')
446+ (invalid', candidates) = validChains cfg m (blocks m)
435447
436448 immutableChainHashes =
437449 map blockHash
438450 . Chain. toOldestFirst
439- . immutableChain secParam
440- $ m
451+ $ immutableChain'
452+
453+ immutableChain' = immutableChain secParam m
441454
442455 extendsImmutableChain :: Chain blk -> Bool
443456 extendsImmutableChain fork =
444457 immutableChainHashes `isPrefixOf`
445458 map blockHash (Chain. toOldestFirst fork)
446459
447460 -- Note that this includes the currently selected chain, but that does not
448- -- influence chain selection via 'selectChain'.
449- consideredCandidates = filter (extendsImmutableChain . fst ) candidates
461+ -- influence chain selection via 'selectChain'. Note that duplicates might
462+ -- be introduced by `trimToLoE` so we deduplicate explicitly here.
463+ consideredCandidates =
464+ candidates
465+ & filter (extendsImmutableChain . fst )
466+ & map (first trimToLoE)
467+ & nubOrdOn (Chain. headPoint . fst )
468+
469+ currentChain' = currentChain m
470+
471+ -- | Trim a candidate fragment to the LoE fragment.
472+ --
473+ -- - A (sanitized) LoE fragment @loe@ is some fragment containing the
474+ -- immutable tip.
475+ --
476+ -- - A candidate fragment @cf@ is valid according to the LoE in one of two
477+ -- cases:
478+ -- - @loe@ is an extension of @cf@.
479+ -- - @cf@ is an extension of @loe@, and @cf@ has at most @k@ blocks after
480+ -- the tip of loe.
481+ --
482+ -- - Trimming a candidate fragment according to the LoE is defined to be the
483+ -- longest prefix that is valid according to the LoE.
484+ --
485+ -- NOTE: It is possible that `trimToLoE a == trimToLoE b` even though `a /=
486+ -- b` if the longest prefix is the same.
487+ trimToLoE :: Chain blk -> Chain blk
488+ trimToLoE candidate =
489+ case loeChain of
490+ LoEDisabled -> candidate
491+ LoEEnabled loeChain' ->
492+ Chain. fromOldestFirst $ go (Chain. toOldestFirst candidate) loePoints
493+ where
494+ loePoints = blockPoint <$> Chain. toOldestFirst loeChain'
495+ where
496+ SecurityParam k = secParam
497+
498+ go :: [blk ] -> [Point blk ] -> [blk ]
499+ -- The LoE chain is an extension of the candidate, return the candidate.
500+ go [] _loePoints = []
501+ -- The candidate is an extension of the LoE chain, return at most the
502+ -- next k blocks on the candidate.
503+ go blks [] = take (fromIntegral k) blks
504+ go (blk : blks) (pt : loePoints)
505+ -- The candidate and the LoE chain agree on the next point, continue
506+ -- recursively.
507+ | blockPoint blk == pt = blk : go blks loePoints
508+ -- The candidate forks off from the LoE chain; stop here.
509+ | otherwise = []
510+
511+ -- If the LoE fragment does not intersect with the current volatile chain,
512+ -- then we use the immutable chain instead.
513+ loeChain =
514+ loeFragment m <&> \ loeFragment' -> fromMaybe immutableChain' $ do
515+ _ <- Fragment. intersect volatileFrag loeFragment'
516+ (_, loeChain') <- Fragment. cross currentFrag loeFragment'
517+ Chain. fromAnchoredFragment loeChain'
518+ where
519+ currentFrag = Chain. toAnchoredFragment currentChain'
520+ volatileFrag = volatileChain secParam id m
450521
451522 newChain :: Chain blk
452523 newLedger :: ExtLedgerState blk
@@ -491,6 +562,18 @@ addBlockPromise cfg blk m = (result, m')
491562 , blockProcessed = return $ SuccesfullyAddedBlock $ tipPoint m'
492563 }
493564
565+ -- | Update the LoE fragment, trigger chain selection and return the new tip
566+ -- point.
567+ updateLoE ::
568+ forall blk . LedgerSupportsProtocol blk
569+ => TopLevelConfig blk
570+ -> AnchoredFragment blk
571+ -> Model blk
572+ -> (Point blk , Model blk )
573+ updateLoE cfg f m = (tipPoint m', m')
574+ where
575+ m' = chainSelection cfg $ m {loeFragment = loeFragment m $> f}
576+
494577{- ------------------------------------------------------------------------------
495578 Iterators
496579-------------------------------------------------------------------------------}
@@ -756,7 +839,6 @@ validate cfg Model { currentSlot, maxClockSkew, initLedger, invalid } chain =
756839 | otherwise
757840 -> findInvalidBlockInTheFuture ledger' bs'
758841
759-
760842chains :: forall blk . (GetPrevHash blk )
761843 => Map (HeaderHash blk ) blk -> [Chain blk ]
762844chains bs = go Chain. Genesis
@@ -1041,3 +1123,26 @@ wipeVolatileDB cfg m =
10411123 -> error " Did not select the ImmutableDB's chain"
10421124
10431125 toHashes = map blockHash . Chain. toOldestFirst
1126+
1127+ -- | Look in the given blocks database for a fragment spanning from the given
1128+ -- anchor to the given hash, and return the fragment in question, or 'Nothing'.
1129+ getFragmentBetween ::
1130+ forall blk . GetPrevHash blk
1131+ => Map (HeaderHash blk ) blk
1132+ -- ^ A map of blocks; usually the 'volatileDbBlocks' of a 'Model'.
1133+ -> Fragment. Anchor blk
1134+ -- ^ The anchor of the fragment to get.
1135+ -> ChainHash blk
1136+ -- ^ The hash of the block to get the fragment up to.
1137+ -> Maybe (AnchoredFragment blk )
1138+ getFragmentBetween bs anchor = go
1139+ where
1140+ go :: ChainHash blk -> Maybe (AnchoredFragment blk )
1141+ go hash | hash == Fragment. anchorToHash anchor =
1142+ Just $ Fragment. Empty anchor
1143+ go GenesisHash =
1144+ Nothing
1145+ go (BlockHash hash) = do
1146+ block <- Map. lookup hash bs
1147+ prevFragment <- go $ blockPrevHash block
1148+ Just $ prevFragment Fragment. :> block
0 commit comments