Skip to content

Commit 6f55a20

Browse files
Merge #1839
1839: ChainDB.StateMachine: testing wiping the VolatileDB r=mrBliss a=mrBliss Fixes #1628. Please review commit per commit. Co-authored-by: Thomas Winant <[email protected]>
2 parents d71cebb + 94bb36e commit 6f55a20

File tree

4 files changed

+405
-169
lines changed

4 files changed

+405
-169
lines changed

ouroboros-consensus/src/Ouroboros/Consensus/Storage/ChainDB/Impl/Reopen.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ reopen (CDBHandle varState) launchBgTasks = do
125125
replayed <- LgrDB.reopen cdbLgrDB cdbImmDB lgrReplayTracer
126126
traceWith cdbTracer $ TraceOpenEvent OpenedLgrDB
127127

128+
-- Drop scheduled chain selections, because we're not guaranteed that
129+
-- these blocks still exist in the VolatileDB.
130+
--
131+
-- See #1880.
132+
atomically $ writeTVar cdbFutureBlocks mempty
133+
128134
curSlot <- atomically $ getCurrentSlot cdbBlockchainTime
129135
chainAndLedger <- initialChainSelection
130136
cdbImmDB

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

Lines changed: 149 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ module Test.Ouroboros.Storage.ChainDB.Model (
2929
, currentLedger
3030
, currentSlot
3131
, lastK
32+
, immutableChain
33+
, immutableBlockNo
34+
, immutableSlotNo
3235
, tipBlock
3336
, tipPoint
3437
, getBlock
3538
, getBlockByPoint
3639
, getBlockComponentByPoint
3740
, hasBlock
3841
, hasBlockByPoint
39-
, immutableBlockNo
4042
, maxSlotNo
4143
, isOpen
4244
, invalid
@@ -59,14 +61,19 @@ module Test.Ouroboros.Storage.ChainDB.Model (
5961
-- * Exported for testing purposes
6062
, between
6163
, blocks
64+
, volDbBlocks
65+
, immDbChain
66+
, futureBlocks
6267
, validChains
6368
, initLedger
6469
, garbageCollectable
6570
, garbageCollectablePoint
6671
, garbageCollectableIteratorNext
6772
, garbageCollect
73+
, copyToImmDB
6874
, closeDB
6975
, reopen
76+
, wipeVolDB
7077
, advanceCurSlot
7178
, chains
7279
) where
@@ -77,7 +84,7 @@ import Control.Monad.Except (runExcept)
7784
import qualified Data.ByteString.Lazy as Lazy
7885
import Data.Function (on)
7986
import Data.Functor.Identity (Identity (..))
80-
import Data.List (sortBy)
87+
import Data.List (isInfixOf, isPrefixOf, sortBy)
8188
import Data.List.NonEmpty (NonEmpty)
8289
import qualified Data.List.NonEmpty as NE
8390
import Data.Map.Strict (Map)
@@ -121,7 +128,10 @@ type LedgerCursorId = Int
121128

122129
-- | Model of the chain DB
123130
data Model blk = Model {
124-
blocks :: Map (HeaderHash blk) blk
131+
volDbBlocks :: Map (HeaderHash blk) blk
132+
-- ^ The VolatileDB
133+
, immDbChain :: Chain blk
134+
-- ^ The ImmutableDB
125135
, cps :: CPS.ChainProducerState blk
126136
, currentLedger :: ExtLedgerState blk
127137
, initLedger :: ExtLedgerState blk
@@ -179,11 +189,20 @@ deriving instance
179189
Queries
180190
-------------------------------------------------------------------------------}
181191

192+
immDbBlocks :: HasHeader blk => Model blk -> Map (HeaderHash blk) blk
193+
immDbBlocks Model { immDbChain } = Map.fromList $
194+
[ (Block.blockHash blk, blk)
195+
| blk <- Chain.toOldestFirst immDbChain
196+
]
197+
198+
blocks :: HasHeader blk => Model blk -> Map (HeaderHash blk) blk
199+
blocks m = volDbBlocks m <> immDbBlocks m
200+
182201
currentChain :: Model blk -> Chain blk
183202
currentChain = CPS.producerChain . cps
184203

185204
getBlock :: HasHeader blk => HeaderHash blk -> Model blk -> Maybe blk
186-
getBlock hash Model{..} = Map.lookup hash blocks
205+
getBlock hash m = Map.lookup hash (blocks m)
187206

188207
hasBlock :: HasHeader blk => HeaderHash blk -> Model blk -> Bool
189208
hasBlock hash = isJust . getBlock hash
@@ -224,17 +243,42 @@ lastK (SecurityParam k) f =
224243
. fmap f
225244
. currentChain
226245

246+
-- | Return the immutable prefix of the current chain.
247+
--
248+
-- This is the longest of the given two chains:
249+
--
250+
-- 1. The current chain with the last @k@ blocks dropped.
251+
-- 2. The chain formed by the blocks in 'immDbChain', i.e., the
252+
-- \"ImmutableDB\". We need to take this case in consideration because the
253+
-- VolatileDB might have been wiped.
254+
--
255+
-- We need this because we do not allow rolling back more than @k@ blocks, but
256+
-- the background thread copying blocks from the VolatileDB to the ImmutableDB
257+
-- might not have caught up yet. This means we cannot use the tip of the
258+
-- ImmutableDB to know the most recent \"immutable\" block.
259+
immutableChain
260+
:: SecurityParam
261+
-> Model blk
262+
-> Chain blk
263+
immutableChain (SecurityParam k) m =
264+
maxBy
265+
Chain.length
266+
(Chain.drop (fromIntegral k) (currentChain m))
267+
(immDbChain m)
268+
where
269+
maxBy f a b
270+
| f a >= f b = a
271+
| otherwise = b
272+
227273
-- | The block number of the most recent \"immutable\" block, i.e. the oldest
228274
-- block we can roll back to. We cannot roll back the block itself.
229275
--
230-
-- In the real implementation this will correspond to the block number of the
231-
-- block at the tip of the Immutable DB.
276+
-- Note that this is not necessarily the block at the tip of the ImmutableDB,
277+
-- because the background thread copying blocks to the ImmutableDB might not
278+
-- have caught up.
232279
immutableBlockNo :: HasHeader blk
233280
=> SecurityParam -> Model blk -> WithOrigin Block.BlockNo
234-
immutableBlockNo (SecurityParam k) =
235-
Chain.headBlockNo
236-
. Chain.drop (fromIntegral k)
237-
. currentChain
281+
immutableBlockNo k = Chain.headBlockNo . immutableChain k
238282

239283
-- | The slot number of the most recent \"immutable\" block (see
240284
-- 'immutableBlockNo').
@@ -245,10 +289,7 @@ immutableSlotNo :: HasHeader blk
245289
=> SecurityParam
246290
-> Model blk
247291
-> WithOrigin SlotNo
248-
immutableSlotNo (SecurityParam k) =
249-
Chain.headSlot
250-
. Chain.drop (fromIntegral k)
251-
. currentChain
292+
immutableSlotNo k = Chain.headSlot . immutableChain k
252293

253294
-- | Get past ledger state
254295
--
@@ -287,7 +328,8 @@ getPastLedger cfg p m@Model{..} =
287328

288329
empty :: ExtLedgerState blk -> Model blk
289330
empty initLedger = Model {
290-
blocks = Map.empty :: Map (HeaderHash blk) blk
331+
volDbBlocks = Map.empty
332+
, immDbChain = Chain.Genesis
291333
, cps = CPS.initChainProducerState Chain.Genesis
292334
, currentLedger = initLedger
293335
, initLedger = initLedger
@@ -336,6 +378,9 @@ addBlock cfg blk m
336378
-- If the block is as old as the tip of the ImmutableDB, i.e. older than
337379
-- @k@, we ignore it, as we can never switch to it.
338380
| olderThanK hdr (isEBB hdr) immBlockNo
381+
= m
382+
-- If it's an invalid block we've seen before, ignore it.
383+
| isKnownInvalid blk
339384
= m
340385
-- The block is from the future, don't add it now, but remember when to
341386
-- add it.
@@ -347,7 +392,8 @@ addBlock cfg blk m
347392
}
348393
| otherwise
349394
= Model {
350-
blocks = blocks'
395+
volDbBlocks = volDbBlocks'
396+
, immDbChain = immDbChain m
351397
, cps = CPS.switchFork newChain (cps m)
352398
, currentLedger = newLedger
353399
, initLedger = initLedger m
@@ -368,12 +414,16 @@ addBlock cfg blk m
368414

369415
slot = Block.blockSlot blk
370416

371-
blocks' :: Map (HeaderHash blk) blk
372-
blocks' = Map.insert (Block.blockHash blk) blk (blocks m)
417+
isKnownInvalid b =
418+
Map.member (Block.blockHash b) (forgetFingerprint (invalid m))
419+
420+
volDbBlocks' :: Map (HeaderHash blk) blk
421+
volDbBlocks' = Map.insert (Block.blockHash blk) blk (volDbBlocks m)
373422

374423
invalidBlocks' :: Map (HeaderHash blk) (InvalidBlockReason blk, SlotNo)
375424
candidates :: [(Chain blk, ExtLedgerState blk)]
376-
(invalidBlocks', candidates) = validChains cfg (initLedger m) blocks'
425+
(invalidBlocks', candidates) =
426+
validChains cfg (initLedger m) (immDbBlocks m <> volDbBlocks')
377427

378428
-- The fingerprint only changes when there are new invalid blocks
379429
fingerprint'
@@ -383,7 +433,16 @@ addBlock cfg blk m
383433
= succ fingerprint
384434
WithFingerprint invalidBlocks fingerprint = invalid m
385435

386-
currentChainFrag = Chain.toAnchoredFragment (currentChain m)
436+
immutableChainHashes =
437+
map Block.blockHash
438+
. Chain.toOldestFirst
439+
. immutableChain secParam
440+
$ m
441+
442+
extendsImmutableChain :: Chain blk -> Bool
443+
extendsImmutableChain fork =
444+
immutableChainHashes `isPrefixOf`
445+
map Block.blockHash (Chain.toOldestFirst fork)
387446

388447
newChain :: Chain blk
389448
newLedger :: ExtLedgerState blk
@@ -393,11 +452,7 @@ addBlock cfg blk m
393452
(selectView (configBlock cfg) . getHeader)
394453
(configConsensus cfg)
395454
(currentChain m)
396-
. filter
397-
( Fragment.forksAtMostKBlocks (maxRollbacks secParam) currentChainFrag
398-
. Chain.toAnchoredFragment
399-
. fst
400-
)
455+
. filter (extendsImmutableChain . fst)
401456
$ candidates
402457

403458
addBlocks :: (LedgerSupportsProtocol blk, ModelSupportsBlock blk)
@@ -726,14 +781,20 @@ between :: forall blk. HasHeader blk
726781
-> Either (UnknownRange blk) [blk]
727782
between (SecurityParam k) from to m = do
728783
fork <- errFork
729-
if Fragment.forksAtMostKBlocks k currentFrag fork
784+
-- See #871.
785+
if partOfCurrentChain fork || Fragment.forksAtMostKBlocks k currentFrag fork
730786
then return $ Fragment.toOldestFirst fork
731787
-- We cannot stream from an old fork
732788
else Left $ ForkTooOld from
733789
where
734790
currentFrag :: AnchoredFragment blk
735791
currentFrag = Chain.toAnchoredFragment (currentChain m)
736792

793+
partOfCurrentChain :: AnchoredFragment blk -> Bool
794+
partOfCurrentChain fork =
795+
map Block.blockPoint (Fragment.toOldestFirst fork) `isInfixOf`
796+
map Block.blockPoint (Chain.toOldestFirst (currentChain m))
797+
737798
-- A fragment for each possible chain in the database
738799
fragments :: [AnchoredFragment blk]
739800
fragments = map Chain.toAnchoredFragment
@@ -805,26 +866,19 @@ between (SecurityParam k) from to m = do
805866
StreamFromExclusive GenesisPoint
806867
-> return frag
807868

808-
-- | Is it possible that the given block is no longer in the ChainDB because
809-
-- the garbage collector has collected it?
869+
-- | Should the given block be garbage collected from the VolatileDB?
810870
--
811-
-- Note that blocks on the current chain will always remain in the ChainDB as
812-
-- they are copied to the ImmutableDB.
813-
--
814-
-- Blocks not on the current chain can be garbage collected from the
815-
-- VolatileDB when their slot number is older than the slot number of the
816-
-- immutable block (the block @k@ blocks after the current tip).
871+
-- Blocks can be garbage collected when their slot number is older than the
872+
-- slot number of the immutable block (the block @k@ blocks after the current
873+
-- tip).
817874
garbageCollectable :: forall blk. HasHeader blk
818875
=> SecurityParam -> Model blk -> blk -> Bool
819876
garbageCollectable secParam m@Model{..} b =
820-
not onCurrentChain && olderThanImmutableSlotNo
821-
where
822-
onCurrentChain = Chain.pointOnChain (Block.blockPoint b) (currentChain m)
823877
-- Note: we don't use the block number but the slot number, as the
824878
-- VolatileDB's garbage collection is in terms of slot numbers.
825-
olderThanImmutableSlotNo = At (Block.blockSlot b) < immutableSlotNo secParam m
879+
At (Block.blockSlot b) < immutableSlotNo secParam m
826880

827-
-- Return 'True' when the model contains the block corresponding to the point
881+
-- | Return 'True' when the model contains the block corresponding to the point
828882
-- and the block itself is eligible for garbage collection, i.e. the real
829883
-- implementation might have garbage collected it.
830884
--
@@ -854,19 +908,71 @@ garbageCollectableIteratorNext secParam m itId =
854908
garbageCollect :: forall blk. HasHeader blk
855909
=> SecurityParam -> Model blk -> Model blk
856910
garbageCollect secParam m@Model{..} = m
857-
{ blocks = Map.filter (not . collectable) blocks
911+
{ volDbBlocks = Map.filter (not . collectable) volDbBlocks
858912
}
859913
-- TODO what about iterators that will stream garbage collected blocks?
860914
where
861915
collectable :: blk -> Bool
862916
collectable = garbageCollectable secParam m
863917

918+
-- | Copy all blocks on the current chain older than @k@ to the \"mock
919+
-- ImmutableDB\" ('immDbChain').
920+
--
921+
-- Idempotent.
922+
copyToImmDB :: SecurityParam -> Model blk -> Model blk
923+
copyToImmDB secParam m = m { immDbChain = immutableChain secParam m }
924+
864925
closeDB :: Model blk -> Model blk
865926
closeDB m@Model{..} = m
866-
{ isOpen = False
867-
, cps = cps { CPS.chainReaders = Map.empty }
868-
, iterators = Map.empty
927+
{ isOpen = False
928+
, cps = cps { CPS.chainReaders = Map.empty }
929+
, iterators = Map.empty
930+
, ledgerCursors = Map.empty
869931
}
870932

871933
reopen :: Model blk -> Model blk
872934
reopen m = m { isOpen = True }
935+
936+
wipeVolDB
937+
:: forall blk. LedgerSupportsProtocol blk
938+
=> TopLevelConfig blk
939+
-> Model blk
940+
-> (Point blk, Model blk)
941+
wipeVolDB cfg m =
942+
(tipPoint m', reopen m')
943+
where
944+
m' = (closeDB m)
945+
{ volDbBlocks = Map.empty
946+
, cps = CPS.switchFork newChain (cps m)
947+
, currentLedger = newLedger
948+
-- Future blocks were in the VolatileDB, so they're now gone
949+
, futureBlocks = Map.empty
950+
, maxSlotNo = NoMaxSlotNo
951+
}
952+
953+
-- Get the chain ending at the ImmutableDB by doing chain selection on the
954+
-- sole candidate (or none) in the ImmutableDB.
955+
newChain :: Chain blk
956+
newLedger :: ExtLedgerState blk
957+
(newChain, newLedger) =
958+
isSameAsImmDbChain
959+
$ selectChain
960+
(selectView (configBlock cfg) . getHeader)
961+
(configConsensus cfg)
962+
Chain.genesis
963+
$ snd
964+
$ validChains cfg (initLedger m) (immDbBlocks m)
965+
966+
isSameAsImmDbChain = \case
967+
Nothing
968+
| Chain.null (immDbChain m)
969+
-> (Chain.Genesis, initLedger m)
970+
| otherwise
971+
-> error "Did not select any chain"
972+
Just res@(chain, _ledger)
973+
| toHashes chain == toHashes (immDbChain m)
974+
-> res
975+
| otherwise
976+
-> error "Did not select the ImmutableDB's chain"
977+
978+
toHashes = map Block.blockHash . Chain.toOldestFirst

0 commit comments

Comments
 (0)