@@ -29,14 +29,16 @@ module Test.Ouroboros.Storage.ChainDB.Model (
29
29
, currentLedger
30
30
, currentSlot
31
31
, lastK
32
+ , immutableChain
33
+ , immutableBlockNo
34
+ , immutableSlotNo
32
35
, tipBlock
33
36
, tipPoint
34
37
, getBlock
35
38
, getBlockByPoint
36
39
, getBlockComponentByPoint
37
40
, hasBlock
38
41
, hasBlockByPoint
39
- , immutableBlockNo
40
42
, maxSlotNo
41
43
, isOpen
42
44
, invalid
@@ -59,14 +61,19 @@ module Test.Ouroboros.Storage.ChainDB.Model (
59
61
-- * Exported for testing purposes
60
62
, between
61
63
, blocks
64
+ , volDbBlocks
65
+ , immDbChain
66
+ , futureBlocks
62
67
, validChains
63
68
, initLedger
64
69
, garbageCollectable
65
70
, garbageCollectablePoint
66
71
, garbageCollectableIteratorNext
67
72
, garbageCollect
73
+ , copyToImmDB
68
74
, closeDB
69
75
, reopen
76
+ , wipeVolDB
70
77
, advanceCurSlot
71
78
, chains
72
79
) where
@@ -77,7 +84,7 @@ import Control.Monad.Except (runExcept)
77
84
import qualified Data.ByteString.Lazy as Lazy
78
85
import Data.Function (on )
79
86
import Data.Functor.Identity (Identity (.. ))
80
- import Data.List (sortBy )
87
+ import Data.List (isInfixOf , isPrefixOf , sortBy )
81
88
import Data.List.NonEmpty (NonEmpty )
82
89
import qualified Data.List.NonEmpty as NE
83
90
import Data.Map.Strict (Map )
@@ -121,7 +128,10 @@ type LedgerCursorId = Int
121
128
122
129
-- | Model of the chain DB
123
130
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
125
135
, cps :: CPS. ChainProducerState blk
126
136
, currentLedger :: ExtLedgerState blk
127
137
, initLedger :: ExtLedgerState blk
@@ -179,11 +189,20 @@ deriving instance
179
189
Queries
180
190
-------------------------------------------------------------------------------}
181
191
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
+
182
201
currentChain :: Model blk -> Chain blk
183
202
currentChain = CPS. producerChain . cps
184
203
185
204
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)
187
206
188
207
hasBlock :: HasHeader blk => HeaderHash blk -> Model blk -> Bool
189
208
hasBlock hash = isJust . getBlock hash
@@ -224,17 +243,42 @@ lastK (SecurityParam k) f =
224
243
. fmap f
225
244
. currentChain
226
245
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
+
227
273
-- | The block number of the most recent \"immutable\" block, i.e. the oldest
228
274
-- block we can roll back to. We cannot roll back the block itself.
229
275
--
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.
232
279
immutableBlockNo :: HasHeader blk
233
280
=> 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
238
282
239
283
-- | The slot number of the most recent \"immutable\" block (see
240
284
-- 'immutableBlockNo').
@@ -245,10 +289,7 @@ immutableSlotNo :: HasHeader blk
245
289
=> SecurityParam
246
290
-> Model blk
247
291
-> WithOrigin SlotNo
248
- immutableSlotNo (SecurityParam k) =
249
- Chain. headSlot
250
- . Chain. drop (fromIntegral k)
251
- . currentChain
292
+ immutableSlotNo k = Chain. headSlot . immutableChain k
252
293
253
294
-- | Get past ledger state
254
295
--
@@ -287,7 +328,8 @@ getPastLedger cfg p m@Model{..} =
287
328
288
329
empty :: ExtLedgerState blk -> Model blk
289
330
empty initLedger = Model {
290
- blocks = Map. empty :: Map (HeaderHash blk ) blk
331
+ volDbBlocks = Map. empty
332
+ , immDbChain = Chain. Genesis
291
333
, cps = CPS. initChainProducerState Chain. Genesis
292
334
, currentLedger = initLedger
293
335
, initLedger = initLedger
@@ -336,6 +378,9 @@ addBlock cfg blk m
336
378
-- If the block is as old as the tip of the ImmutableDB, i.e. older than
337
379
-- @k@, we ignore it, as we can never switch to it.
338
380
| olderThanK hdr (isEBB hdr) immBlockNo
381
+ = m
382
+ -- If it's an invalid block we've seen before, ignore it.
383
+ | isKnownInvalid blk
339
384
= m
340
385
-- The block is from the future, don't add it now, but remember when to
341
386
-- add it.
@@ -347,7 +392,8 @@ addBlock cfg blk m
347
392
}
348
393
| otherwise
349
394
= Model {
350
- blocks = blocks'
395
+ volDbBlocks = volDbBlocks'
396
+ , immDbChain = immDbChain m
351
397
, cps = CPS. switchFork newChain (cps m)
352
398
, currentLedger = newLedger
353
399
, initLedger = initLedger m
@@ -368,12 +414,16 @@ addBlock cfg blk m
368
414
369
415
slot = Block. blockSlot blk
370
416
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)
373
422
374
423
invalidBlocks' :: Map (HeaderHash blk ) (InvalidBlockReason blk , SlotNo )
375
424
candidates :: [(Chain blk , ExtLedgerState blk )]
376
- (invalidBlocks', candidates) = validChains cfg (initLedger m) blocks'
425
+ (invalidBlocks', candidates) =
426
+ validChains cfg (initLedger m) (immDbBlocks m <> volDbBlocks')
377
427
378
428
-- The fingerprint only changes when there are new invalid blocks
379
429
fingerprint'
@@ -383,7 +433,16 @@ addBlock cfg blk m
383
433
= succ fingerprint
384
434
WithFingerprint invalidBlocks fingerprint = invalid m
385
435
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)
387
446
388
447
newChain :: Chain blk
389
448
newLedger :: ExtLedgerState blk
@@ -393,11 +452,7 @@ addBlock cfg blk m
393
452
(selectView (configBlock cfg) . getHeader)
394
453
(configConsensus cfg)
395
454
(currentChain m)
396
- . filter
397
- ( Fragment. forksAtMostKBlocks (maxRollbacks secParam) currentChainFrag
398
- . Chain. toAnchoredFragment
399
- . fst
400
- )
455
+ . filter (extendsImmutableChain . fst )
401
456
$ candidates
402
457
403
458
addBlocks :: (LedgerSupportsProtocol blk , ModelSupportsBlock blk )
@@ -726,14 +781,20 @@ between :: forall blk. HasHeader blk
726
781
-> Either (UnknownRange blk ) [blk ]
727
782
between (SecurityParam k) from to m = do
728
783
fork <- errFork
729
- if Fragment. forksAtMostKBlocks k currentFrag fork
784
+ -- See #871.
785
+ if partOfCurrentChain fork || Fragment. forksAtMostKBlocks k currentFrag fork
730
786
then return $ Fragment. toOldestFirst fork
731
787
-- We cannot stream from an old fork
732
788
else Left $ ForkTooOld from
733
789
where
734
790
currentFrag :: AnchoredFragment blk
735
791
currentFrag = Chain. toAnchoredFragment (currentChain m)
736
792
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
+
737
798
-- A fragment for each possible chain in the database
738
799
fragments :: [AnchoredFragment blk ]
739
800
fragments = map Chain. toAnchoredFragment
@@ -805,26 +866,19 @@ between (SecurityParam k) from to m = do
805
866
StreamFromExclusive GenesisPoint
806
867
-> return frag
807
868
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?
810
870
--
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).
817
874
garbageCollectable :: forall blk . HasHeader blk
818
875
=> SecurityParam -> Model blk -> blk -> Bool
819
876
garbageCollectable secParam m@ Model {.. } b =
820
- not onCurrentChain && olderThanImmutableSlotNo
821
- where
822
- onCurrentChain = Chain. pointOnChain (Block. blockPoint b) (currentChain m)
823
877
-- Note: we don't use the block number but the slot number, as the
824
878
-- 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
826
880
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
828
882
-- and the block itself is eligible for garbage collection, i.e. the real
829
883
-- implementation might have garbage collected it.
830
884
--
@@ -854,19 +908,71 @@ garbageCollectableIteratorNext secParam m itId =
854
908
garbageCollect :: forall blk . HasHeader blk
855
909
=> SecurityParam -> Model blk -> Model blk
856
910
garbageCollect secParam m@ Model {.. } = m
857
- { blocks = Map. filter (not . collectable) blocks
911
+ { volDbBlocks = Map. filter (not . collectable) volDbBlocks
858
912
}
859
913
-- TODO what about iterators that will stream garbage collected blocks?
860
914
where
861
915
collectable :: blk -> Bool
862
916
collectable = garbageCollectable secParam m
863
917
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
+
864
925
closeDB :: Model blk -> Model blk
865
926
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
869
931
}
870
932
871
933
reopen :: Model blk -> Model blk
872
934
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