Skip to content

Commit 1357c1b

Browse files
amesgenNiols
andcommitted
ChainDB q-s-m test: add the LoE
Co-authored-by: Nicolas “Niols” Jeannerod <[email protected]>
1 parent 6442d81 commit 1357c1b

File tree

7 files changed

+295
-83
lines changed

7 files changed

+295
-83
lines changed

ouroboros-consensus/src/unstable-consensus-testlib/Test/Util/Orphans/Arbitrary.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import Ouroboros.Consensus.Ledger.Query
5353
import Ouroboros.Consensus.Ledger.SupportsMempool
5454
import Ouroboros.Consensus.Node.ProtocolInfo
5555
import Ouroboros.Consensus.Protocol.Abstract (ChainDepState)
56+
import Ouroboros.Consensus.Storage.ChainDB.API (LoE (..))
5657
import Ouroboros.Consensus.Storage.ImmutableDB.Chunks.Internal
5758
(ChunkNo (..), ChunkSize (..), RelativeSlot (..))
5859
import Ouroboros.Consensus.Storage.ImmutableDB.Chunks.Layout
@@ -407,3 +408,12 @@ instance Arbitrary Index.CacheConfig where
407408
-- TODO create a Cmd that advances time, so this is being exercised too.
408409
expireUnusedAfter <- (fromIntegral :: Int -> DiffTime) <$> choose (1, 100)
409410
return Index.CacheConfig {Index.pastChunksToCache, Index.expireUnusedAfter}
411+
412+
{-------------------------------------------------------------------------------
413+
LoE
414+
-------------------------------------------------------------------------------}
415+
416+
instance Arbitrary a => Arbitrary (LoE a) where
417+
arbitrary = oneof [pure LoEDisabled, LoEEnabled <$> arbitrary]
418+
shrink LoEDisabled = []
419+
shrink (LoEEnabled x) = LoEDisabled : map LoEEnabled (shrink x)

ouroboros-consensus/src/unstable-consensus-testlib/Test/Util/Orphans/ToExpr.hs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE DeriveGeneric #-}
33
{-# LANGUAGE DerivingStrategies #-}
44
{-# LANGUAGE FlexibleContexts #-}
5+
{-# LANGUAGE FlexibleInstances #-}
56
{-# LANGUAGE StandaloneDeriving #-}
67
{-# LANGUAGE UndecidableInstances #-}
78

@@ -18,9 +19,12 @@ import Ouroboros.Consensus.Ledger.Abstract
1819
import Ouroboros.Consensus.Ledger.Extended
1920
import Ouroboros.Consensus.Protocol.Abstract
2021
import Ouroboros.Consensus.Storage.ChainDB (InvalidBlockReason)
22+
import Ouroboros.Consensus.Storage.ChainDB.API (LoE (..))
2123
import Ouroboros.Consensus.Storage.ChainDB.Impl.LgrDB
2224
import Ouroboros.Consensus.Storage.ImmutableDB
2325
import Ouroboros.Consensus.Util.STM (Fingerprint, WithFingerprint)
26+
import Ouroboros.Network.AnchoredFragment (AnchoredFragment)
27+
import qualified Ouroboros.Network.AnchoredFragment as Fragment
2428
import Ouroboros.Network.Block (MaxSlotNo)
2529
import Ouroboros.Network.Mock.Chain
2630
import Ouroboros.Network.Mock.ProducerState
@@ -37,6 +41,14 @@ instance ToExpr (HeaderHash blk) => ToExpr (Point blk)
3741
instance ToExpr (HeaderHash blk) => ToExpr (RealPoint blk)
3842
instance (ToExpr slot, ToExpr hash) => ToExpr (Block slot hash)
3943

44+
deriving instance ( ToExpr blk
45+
, ToExpr (HeaderHash blk)
46+
)
47+
=> ToExpr (Fragment.Anchor blk)
48+
49+
instance (ToExpr blk, ToExpr (HeaderHash blk)) => ToExpr (AnchoredFragment blk) where
50+
toExpr f = toExpr (Fragment.anchor f, Fragment.toOldestFirst f)
51+
4052
{-------------------------------------------------------------------------------
4153
ouroboros-consensus
4254
-------------------------------------------------------------------------------}
@@ -73,6 +85,8 @@ instance ToExpr ChunkInfo where
7385
instance ToExpr FsError where
7486
toExpr fsError = App (show fsError) []
7587

88+
deriving instance ToExpr a => ToExpr (LoE a)
89+
7690

7791
{-------------------------------------------------------------------------------
7892
si-timers

ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/ChainDB/Model.hs

Lines changed: 138 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (
8183
import Codec.Serialise (Serialise, serialise)
8284
import Control.Monad (unless)
8385
import Control.Monad.Except (runExcept)
86+
import Data.Bifunctor (first)
8487
import 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 (($>), (<&>))
8691
import Data.List (isInfixOf, isPrefixOf, sortBy)
8792
import Data.Map.Strict (Map)
8893
import qualified Data.Map.Strict as Map
@@ -104,8 +109,8 @@ import Ouroboros.Consensus.Protocol.MockChainSel
104109
import 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)
109114
import Ouroboros.Consensus.Storage.ChainDB.Impl.ChainSel (olderThanK)
110115
import Ouroboros.Consensus.Storage.LedgerDB
111116
import Ouroboros.Consensus.Util (repeatedly)
@@ -119,7 +124,7 @@ import qualified Ouroboros.Network.Mock.Chain as Chain
119124
import Ouroboros.Network.Mock.ProducerState (ChainProducerState)
120125
import qualified Ouroboros.Network.Mock.ProducerState as CPS
121126
import Test.Cardano.Slotting.TreeDiff ()
122-
127+
import Test.Util.Orphans.ToExpr ()
123128

124129
type 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

366372
empty ::
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-
760842
chains :: forall blk. (GetPrevHash blk)
761843
=> Map (HeaderHash blk) blk -> [Chain blk]
762844
chains 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

Comments
 (0)