Skip to content

Commit 287955b

Browse files
committed
PerasCertDB: implement garbage collection
1 parent d795fb3 commit 287955b

File tree

4 files changed

+100
-22
lines changed

4 files changed

+100
-22
lines changed

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/PerasCertDB/API.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import qualified Ouroboros.Network.AnchoredFragment as AF
2323
data PerasCertDB m blk = PerasCertDB
2424
{ addCert :: PerasCert blk -> m ()
2525
, getWeightSnapshot :: STM m (PerasWeightSnapshot blk)
26+
, garbageCollect :: SlotNo -> m ()
27+
-- ^ Garbage-collect state older than the given slot number.
2628
, closeDB :: m ()
2729
}
2830
deriving NoThunks via OnlyCheckWhnfNamed "PerasCertDB" (PerasCertDB m blk)

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/PerasCertDB/Impl.hs

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ module Ouroboros.Consensus.Storage.PerasCertDB.Impl
2323
import Control.Monad (join)
2424
import Control.Tracer (Tracer, nullTracer, traceWith)
2525
import Data.Kind (Type)
26+
import qualified Data.Map.Merge.Strict as Map
2627
import Data.Map.Strict (Map)
2728
import qualified Data.Map.Strict as Map
28-
import Data.Set (Set)
29-
import qualified Data.Set as Set
3029
import GHC.Generics (Generic)
3130
import NoThunks.Class
3231
import Ouroboros.Consensus.Block
@@ -58,20 +57,19 @@ openDB ::
5857
Complete PerasCertDbArgs m blk ->
5958
m (PerasCertDB m blk)
6059
openDB args = do
61-
pcdbRoundNos <- newTVarIO Set.empty
62-
pcdbWeightByPoint <- newTVarIO Map.empty
60+
pcdbVolatileState <- newTVarIO initialPerasVolatileCertState
6361
let env =
6462
PerasCertDbEnv
6563
{ pcdbTracer
66-
, pcdbRoundNos
67-
, pcdbWeightByPoint
64+
, pcdbVolatileState
6865
}
6966
h <- PerasCertDbHandle <$> newTVarIO (PerasCertDbOpen env)
7067
traceWith pcdbTracer OpenedPerasCertDB
7168
pure
7269
PerasCertDB
7370
{ addCert = getEnv1 h implAddCert
7471
, getWeightSnapshot = getEnvSTM h implGetWeightSnapshot
72+
, garbageCollect = getEnv1 h implGarbageCollect
7573
, closeDB = implCloseDB h
7674
}
7775
where
@@ -93,13 +91,8 @@ data PerasCertDbState m blk
9391

9492
data PerasCertDbEnv m blk = PerasCertDbEnv
9593
{ pcdbTracer :: !(Tracer m (TraceEvent blk))
96-
, pcdbRoundNos :: !(StrictTVar m (Set PerasRoundNo))
94+
, pcdbVolatileState :: !(StrictTVar m (PerasVolatileCertState blk))
9795
-- ^ The 'RoundNo's of all certificates currently in the db.
98-
, pcdbWeightByPoint :: !(StrictTVar m (Map (Point blk) PerasWeight))
99-
-- ^ The weight of boosted blocks w.r.t. the certificates currently in the
100-
-- db.
101-
--
102-
-- INVARIANT: In sync with 'pcdbRoundNos'.
10396
}
10497
deriving NoThunks via OnlyCheckWhnfNamed "PerasCertDbEnv" (PerasCertDbEnv m blk)
10598

@@ -154,20 +147,25 @@ implAddCert ::
154147
implAddCert env cert = do
155148
traceWith pcdbTracer $ AddingPerasCert roundNo boostedPt
156149
join $ atomically $ do
157-
roundNos <- readTVar pcdbRoundNos
158-
if Set.member roundNo roundNos
150+
PerasVolatileCertState{pvcsCerts, pvcsWeightByPoint} <- readTVar pcdbVolatileState
151+
if Map.member roundNo pvcsCerts
159152
then do
160153
pure $ traceWith pcdbTracer $ IgnoredCertAlreadyInDB roundNo boostedPt
161154
else do
162-
writeTVar pcdbRoundNos $ Set.insert roundNo roundNos
163-
-- Note that the same block might be boosted by multiple points.
164-
modifyTVar pcdbWeightByPoint $ Map.insertWith (<>) boostedPt boostPerCert
155+
writeTVar
156+
pcdbVolatileState
157+
PerasVolatileCertState
158+
{ pvcsCerts =
159+
Map.insert roundNo cert pvcsCerts
160+
, -- Note that the same block might be boosted by multiple points.
161+
pvcsWeightByPoint =
162+
Map.insertWith (<>) boostedPt boostPerCert pvcsWeightByPoint
163+
}
165164
pure $ traceWith pcdbTracer $ AddedPerasCert roundNo boostedPt
166165
where
167166
PerasCertDbEnv
168167
{ pcdbTracer
169-
, pcdbRoundNos
170-
, pcdbWeightByPoint
168+
, pcdbVolatileState
171169
} = env
172170

173171
roundNo = perasCertRound cert
@@ -176,8 +174,72 @@ implAddCert env cert = do
176174
implGetWeightSnapshot ::
177175
IOLike m =>
178176
PerasCertDbEnv m blk -> STM m (PerasWeightSnapshot blk)
179-
implGetWeightSnapshot PerasCertDbEnv{pcdbWeightByPoint} =
180-
PerasWeightSnapshot <$> readTVar pcdbWeightByPoint
177+
implGetWeightSnapshot PerasCertDbEnv{pcdbVolatileState} =
178+
PerasWeightSnapshot . pvcsWeightByPoint <$> readTVar pcdbVolatileState
179+
180+
implGarbageCollect ::
181+
forall m blk.
182+
(IOLike m, StandardHash blk) =>
183+
PerasCertDbEnv m blk -> SlotNo -> m ()
184+
implGarbageCollect PerasCertDbEnv{pcdbVolatileState} slot =
185+
atomically $ modifyTVar pcdbVolatileState gc
186+
where
187+
gc :: PerasVolatileCertState blk -> PerasVolatileCertState blk
188+
gc PerasVolatileCertState{pvcsCerts, pvcsWeightByPoint} =
189+
PerasVolatileCertState
190+
{ pvcsCerts = certsToKeep
191+
, pvcsWeightByPoint =
192+
Map.merge
193+
-- Do not touch weight of boosted blocks that we do not subtract any
194+
-- weight from.
195+
Map.preserveMissing
196+
-- Irrelevant, the key set of @weightToRemove@ is a subset of the
197+
-- key set of @pvcsWeightByPoint@.
198+
Map.dropMissing
199+
(Map.zipWithMaybeMatched $ \_pt -> subtractWeight)
200+
pvcsWeightByPoint
201+
weightToRemove
202+
}
203+
where
204+
(certsToRemove, certsToKeep) =
205+
Map.partition isTooOld pvcsCerts
206+
isTooOld cert =
207+
pointSlot (perasCertBoostedBlock cert) < NotOrigin slot
208+
weightToRemove =
209+
Map.fromListWith
210+
(<>)
211+
[ (perasCertBoostedBlock cert, boostPerCert)
212+
| cert <- Map.elems certsToRemove
213+
]
214+
215+
subtractWeight :: PerasWeight -> PerasWeight -> Maybe PerasWeight
216+
subtractWeight (PerasWeight w1) (PerasWeight w2)
217+
| w1 > w2 = Just $ PerasWeight (w1 - w2)
218+
| otherwise = Nothing
219+
220+
{-------------------------------------------------------------------------------
221+
Implementation-internal types
222+
-------------------------------------------------------------------------------}
223+
224+
-- | Volatile Peras certificate state, i.e. certificates that could influence
225+
-- chain selection by boosting a volatile block.
226+
data PerasVolatileCertState blk = PerasVolatileCertState
227+
{ pvcsCerts :: !(Map PerasRoundNo (PerasCert blk))
228+
-- ^ The boosted blocks by 'RoundNo' of all certificates currently in the db.
229+
, pvcsWeightByPoint :: !(Map (Point blk) PerasWeight)
230+
-- ^ The weight of boosted blocks w.r.t. the certificates currently in the db.
231+
--
232+
-- INVARIANT: In sync with 'pvcsCerts'.
233+
}
234+
deriving stock (Show, Generic)
235+
deriving anyclass NoThunks
236+
237+
initialPerasVolatileCertState :: PerasVolatileCertState blk
238+
initialPerasVolatileCertState =
239+
PerasVolatileCertState
240+
{ pvcsCerts = Map.empty
241+
, pvcsWeightByPoint = Map.empty
242+
}
181243

182244
{-------------------------------------------------------------------------------
183245
Trace types

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ module Test.Ouroboros.Storage.PerasCertDB.Model
1111
, closeDB
1212
, addCert
1313
, getWeightSnapshot
14+
, garbageCollect
1415
) where
1516

1617
import qualified Data.Map as Map
1718
import Data.Set (Set)
1819
import qualified Data.Set as Set
1920
import GHC.Generics (Generic)
20-
import Ouroboros.Consensus.Block (PerasCert, StandardHash, boostPerCert, perasCertBoostedBlock)
21+
import Ouroboros.Consensus.Block
2122
import Ouroboros.Consensus.Storage.PerasCertDB.API (PerasWeightSnapshot (..))
2223

2324
data Model blk = Model
@@ -56,3 +57,9 @@ getWeightSnapshot Model{certs} = snap
5657
Map.empty
5758
certs
5859
}
60+
61+
garbageCollect :: StandardHash blk => SlotNo -> Model blk -> Model blk
62+
garbageCollect slot model@Model{certs} =
63+
model{certs = Set.filter keepCert certs}
64+
where
65+
keepCert cert = pointSlot (perasCertBoostedBlock cert) >= NotOrigin slot

ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/PerasCertDB/StateMachine.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ instance StateModel Model where
5151
CloseDB :: Action Model ()
5252
AddCert :: PerasCert TestBlock -> Action Model ()
5353
GetWeightSnapshot :: Action Model (PerasWeightSnapshot TestBlock)
54+
GarbageCollect :: SlotNo -> Action Model ()
5455

5556
arbitraryAction _ (Model model)
5657
| model.open =
5758
frequency
5859
[ (1, pure $ Some CloseDB)
5960
, (20, Some <$> genAddCert)
6061
, (20, pure $ Some GetWeightSnapshot)
62+
, (5, Some . GarbageCollect . SlotNo <$> arbitrary)
6163
]
6264
| otherwise = pure $ Some OpenDB
6365
where
@@ -82,6 +84,7 @@ instance StateModel Model where
8284
CloseDB -> Model.closeDB model
8385
AddCert cert -> Model.addCert model cert
8486
GetWeightSnapshot -> model
87+
GarbageCollect slot -> Model.garbageCollect slot model
8588

8689
precondition (Model model) = \case
8790
OpenDB -> not model.open
@@ -93,6 +96,7 @@ instance StateModel Model where
9396
where
9497
p cert' = perasCertRound cert /= perasCertRound cert' || cert == cert'
9598
GetWeightSnapshot -> True
99+
GarbageCollect _slot -> True
96100

97101
deriving stock instance Show (Action Model a)
98102
deriving stock instance Eq (Action Model a)
@@ -114,6 +118,9 @@ instance RunModel Model (StateT (PerasCertDB IO TestBlock) IO) where
114118
GetWeightSnapshot -> do
115119
perasCertDB <- get
116120
lift $ atomically $ PerasCertDB.getWeightSnapshot perasCertDB
121+
GarbageCollect slot -> do
122+
perasCertDB <- get
123+
lift $ PerasCertDB.garbageCollect perasCertDB slot
117124

118125
postcondition (Model model, _) GetWeightSnapshot _ actual = do
119126
let expected = Model.getWeightSnapshot model

0 commit comments

Comments
 (0)