Skip to content

Commit 17b9132

Browse files
committed
Generate security parameter for ChainDB q-s-m test on the fly
After analysing the effect of varying the security parameter (`k`) of the ChainDB state machine tests (currently hardcoded with 2), we have observed a tension between: 1) generating enough tests exercising the new Peras behavior where the chain selection mechanism switches to a shorter but heavier chain (cert boost is derived from k and must be large enough to overcome the weight of a longer chain), and 2) generating enough tests exercising the ImmutableDB logic (the chain must have at least k blocks) Here are some empirical results: k -> P(switch to shorter chain), P(generate a chain with >= k blocks) k=2 -> ~1.3%, ~40% k=3 -> ~1.9%, ~20% k=4 -> ~2.4%, ~9% k=5 -> ~2.5%, ~3% k=10 -> ~3%, ~0.05% We believe that the sweet spot between both desiderata appears to be around `k=2` and `k=4`. This commit introduces a random generator for `k` using a geometric distribution to bias the randomly generated `k`s to be relatively small, while still allowing larger ones to appear from time to time. Under the current parameters, roughly 75% of the tests use `k<=4`; ``` Security Parameter (k) (10000 in total): 50.82% 2 23.83% 3 12.62% 4 6.69% 5 3.08% 6 1.54% 7 0.74% 8 0.37% 9 0.16% 10 0.06% 11 0.05% 12 0.02% 13 0.01% 14 0.01% 17 ``` Yielding the following distributions for 1) and 2), respectively: ``` Tags (5161 in total): 39.35% TagGetIsValidJust 29.22% TagChainSelReprocessKeptSelection 25.91% TagGetIsValidNothing 3.88% TagChainSelReprocessChangedSelection 1.65% TagSwitchedToShorterChain <- HERE ``` ``` Chain length >= k (10000 in total): 73.25% False 26.75% True <- HERE ```
1 parent 495247e commit 17b9132

File tree

3 files changed

+63
-31
lines changed

3 files changed

+63
-31
lines changed

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

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ module Test.Ouroboros.Storage.ChainDB.StateMachine
7373
, tests
7474
) where
7575

76-
import Cardano.Ledger.BaseTypes (knownNonZeroBounded)
76+
import Cardano.Ledger.BaseTypes (unNonZero, unsafeNonZero)
7777
import Codec.Serialise (Serialise)
7878
import Control.Monad (replicateM, void)
7979
import Control.ResourceRegistry
@@ -151,6 +151,7 @@ import qualified Test.Ouroboros.Storage.ChainDB.Model as Model
151151
import Test.Ouroboros.Storage.Orphans ()
152152
import Test.Ouroboros.Storage.TestBlock
153153
import Test.QuickCheck hiding (forAll)
154+
import qualified Test.QuickCheck as QC
154155
import qualified Test.QuickCheck.Monadic as QC
155156
import Test.StateMachine
156157
import qualified Test.StateMachine.Labelling as C
@@ -1709,56 +1710,75 @@ genBlk chunkInfo Model{..} =
17091710
)
17101711
]
17111712

1713+
genSecurityParam :: Gen SecurityParam
1714+
genSecurityParam =
1715+
SecurityParam
1716+
. unsafeNonZero
1717+
. fromIntegral
1718+
. (+ 2) -- shift to the right to avoid degenerate cases
1719+
<$> geometric 0.5 -- range in [0, +inf); mean = 1/p = 2
1720+
where
1721+
geometric :: Double -> Gen Int
1722+
geometric p
1723+
| p <= 0 || p > 1 = error "p must be in (0,1]"
1724+
| otherwise = do
1725+
u <- choose (0.0, 1.0)
1726+
let k = floor (log u / log (1 - p))
1727+
return k
1728+
17121729
{-------------------------------------------------------------------------------
17131730
Top-level tests
17141731
-------------------------------------------------------------------------------}
17151732

1716-
mkTestCfg :: ImmutableDB.ChunkInfo -> TopLevelConfig TestBlock
1717-
mkTestCfg (ImmutableDB.UniformChunkSize chunkSize) =
1718-
mkTestConfig (SecurityParam $ knownNonZeroBounded @2) chunkSize
1733+
mkTestCfg :: SecurityParam -> ImmutableDB.ChunkInfo -> TopLevelConfig TestBlock
1734+
mkTestCfg k (ImmutableDB.UniformChunkSize chunkSize) =
1735+
mkTestConfig k chunkSize
17191736

17201737
envUnused :: ChainDBEnv m blk
17211738
envUnused = error "ChainDBEnv used during command generation"
17221739

17231740
smUnused ::
17241741
LoE () ->
1742+
SecurityParam ->
17251743
ImmutableDB.ChunkInfo ->
17261744
StateMachine (Model Blk IO) (At Cmd Blk IO) IO (At Resp Blk IO)
1727-
smUnused loe chunkInfo =
1745+
smUnused loe k chunkInfo =
17281746
sm
17291747
loe
17301748
envUnused
17311749
(genBlk chunkInfo)
1732-
(mkTestCfg chunkInfo)
1750+
(mkTestCfg k chunkInfo)
17331751
testInitExtLedger
17341752

17351753
prop_sequential :: LoE () -> SmallChunkInfo -> Property
17361754
prop_sequential loe smallChunkInfo@(SmallChunkInfo chunkInfo) =
1737-
forAllCommands (smUnused loe chunkInfo) Nothing $
1738-
runCmdsLockstep loe smallChunkInfo
1755+
QC.forAll genSecurityParam $ \k ->
1756+
forAllCommands (smUnused loe k chunkInfo) Nothing $
1757+
runCmdsLockstep loe k smallChunkInfo
17391758

17401759
runCmdsLockstep ::
17411760
LoE () ->
1761+
SecurityParam ->
17421762
SmallChunkInfo ->
17431763
QSM.Commands (At Cmd Blk IO) (At Resp Blk IO) ->
17441764
Property
1745-
runCmdsLockstep loe (SmallChunkInfo chunkInfo) cmds =
1765+
runCmdsLockstep loe k (SmallChunkInfo chunkInfo) cmds =
17461766
QC.monadicIO $ do
17471767
let
17481768
-- Current test case command names.
17491769
ctcCmdNames :: [String]
17501770
ctcCmdNames = fmap (show . cmdName . QSM.getCommand) $ QSM.unCommands cmds
17511771

17521772
(hist, prop) <- QC.run $ test cmds
1753-
prettyCommands (smUnused loe chunkInfo) hist
1773+
prettyCommands (smUnused loe k chunkInfo) hist
17541774
$ tabulate
17551775
"Tags"
1756-
(map show $ tag (execCmds (QSM.initModel (smUnused loe chunkInfo)) cmds))
1776+
(map show $ tag (execCmds (QSM.initModel (smUnused loe k chunkInfo)) cmds))
17571777
$ tabulate "Command sequence length" [show $ length ctcCmdNames]
17581778
$ tabulate "Commands" ctcCmdNames
17591779
$ prop
17601780
where
1761-
testCfg = mkTestCfg chunkInfo
1781+
testCfg = mkTestCfg k chunkInfo
17621782

17631783
test ::
17641784
QSM.Commands (At Cmd Blk IO) (At Resp Blk IO) ->
@@ -1821,26 +1841,30 @@ runCmdsLockstep loe (SmallChunkInfo chunkInfo) cmds =
18211841
fses <- atomically $ traverse readTMVar nodeDBs
18221842
let
18231843
modelChain = Model.currentChain $ dbModel model
1844+
secParam = unNonZero (maxRollbacks (configSecurityParam testCfg))
18241845
prop =
18251846
counterexample (show (configSecurityParam testCfg)) $
18261847
counterexample ("Model chain: " <> condense modelChain) $
18271848
counterexample ("TraceEvents: " <> unlines (map show trace)) $
18281849
tabulate "Chain length" [show (Chain.length modelChain)] $
1829-
tabulate "TraceEvents" (map traceEventName trace) $
1830-
res === Ok
1831-
.&&. prop_trace testCfg (dbModel model) trace
1832-
.&&. counterexample
1833-
"ImmutableDB is leaking file handles"
1834-
(Mock.numOpenHandles (nodeDBsImm fses) === 0)
1835-
.&&. counterexample
1836-
"VolatileDB is leaking file handles"
1837-
(Mock.numOpenHandles (nodeDBsVol fses) === 0)
1838-
.&&. counterexample
1839-
"LedgerDB is leaking file handles"
1840-
(Mock.numOpenHandles (nodeDBsLgr fses) === 0)
1841-
.&&. counterexample
1842-
"There were registered clean-up actions"
1843-
(remainingCleanups === 0)
1850+
tabulate "Security Parameter (k)" [show secParam] $
1851+
tabulate "Chain length >= k" [show (Chain.length modelChain >= fromIntegral secParam)] $
1852+
tabulate "TraceEvents" (map traceEventName trace) $
1853+
res
1854+
=== Ok
1855+
.&&. prop_trace testCfg (dbModel model) trace
1856+
.&&. counterexample
1857+
"ImmutableDB is leaking file handles"
1858+
(Mock.numOpenHandles (nodeDBsImm fses) === 0)
1859+
.&&. counterexample
1860+
"VolatileDB is leaking file handles"
1861+
(Mock.numOpenHandles (nodeDBsVol fses) === 0)
1862+
.&&. counterexample
1863+
"LedgerDB is leaking file handles"
1864+
(Mock.numOpenHandles (nodeDBsLgr fses) === 0)
1865+
.&&. counterexample
1866+
"There were registered clean-up actions"
1867+
(remainingCleanups === 0)
18441868
return (hist, prop)
18451869

18461870
prop_trace :: TopLevelConfig Blk -> DBModel Blk -> [TraceEvent Blk] -> Property

ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/ChainDB/StateMachine/Utils/RunOnRepl.hs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ import Ouroboros.Consensus.Block
8787
, EpochNo (EpochNo)
8888
, SlotNo (SlotNo)
8989
)
90+
import Ouroboros.Consensus.Config (SecurityParam)
9091
import Ouroboros.Consensus.Storage.ChainDB
9192
( ChainType (TentativeChain)
9293
, LoE
@@ -142,8 +143,9 @@ pattern Command cmd rsp xs =
142143

143144
quickCheckCmdsLockStep ::
144145
LoE () ->
146+
SecurityParam ->
145147
SmallChunkInfo ->
146148
Commands (StateMachine.At Cmd TestBlock IO) (StateMachine.At Resp TestBlock IO) ->
147149
IO ()
148-
quickCheckCmdsLockStep loe chunkInfo cmds =
149-
quickCheck $ runCmdsLockstep loe chunkInfo cmds
150+
quickCheckCmdsLockStep loe k chunkInfo cmds =
151+
quickCheck $ runCmdsLockstep loe k chunkInfo cmds

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE ConstraintKinds #-}
2+
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE DerivingStrategies #-}
34
{-# LANGUAGE FlexibleContexts #-}
45
{-# LANGUAGE FlexibleInstances #-}
@@ -7,12 +8,14 @@
78
{-# LANGUAGE MultiParamTypeClasses #-}
89
{-# LANGUAGE NamedFieldPuns #-}
910
{-# LANGUAGE RankNTypes #-}
11+
{-# LANGUAGE TypeApplications #-}
1012
{-# LANGUAGE TypeFamilies #-}
1113
{-# LANGUAGE TypeOperators #-}
1214
{-# LANGUAGE UndecidableInstances #-}
1315

1416
module Test.Ouroboros.Storage.ChainDB.Unit (tests) where
1517

18+
import Cardano.Ledger.BaseTypes (knownNonZeroBounded)
1619
import Cardano.Slotting.Slot (WithOrigin (..))
1720
import Control.Monad (replicateM, unless, void)
1821
import Control.Monad.Except
@@ -35,6 +38,7 @@ import Ouroboros.Consensus.Config
3538
( TopLevelConfig
3639
, configSecurityParam
3740
)
41+
import Ouroboros.Consensus.Config.SecurityParam (SecurityParam (..))
3842
import Ouroboros.Consensus.Ledger.Basics
3943
import Ouroboros.Consensus.Ledger.Extended (ExtLedgerState)
4044
import Ouroboros.Consensus.Ledger.SupportsProtocol
@@ -243,16 +247,18 @@ runModelIO :: API.LoE () -> ModelM TestBlock a -> IO ()
243247
runModelIO loe expr = toAssertion (runModel newModel topLevelConfig expr)
244248
where
245249
chunkInfo = ImmutableDB.simpleChunkInfo 100
250+
k = SecurityParam (knownNonZeroBounded @2)
246251
newModel = Model.empty loe testInitExtLedger
247-
topLevelConfig = mkTestCfg chunkInfo
252+
topLevelConfig = mkTestCfg k chunkInfo
248253

249254
-- | Helper function to run the test against the actual chain database and
250255
-- translate to something that HUnit likes.
251256
runSystemIO :: SystemM TestBlock IO a -> IO ()
252257
runSystemIO expr = runSystem withChainDbEnv expr >>= toAssertion
253258
where
254259
chunkInfo = ImmutableDB.simpleChunkInfo 100
255-
topLevelConfig = mkTestCfg chunkInfo
260+
k = SecurityParam (knownNonZeroBounded @2)
261+
topLevelConfig = mkTestCfg k chunkInfo
256262
withChainDbEnv = withTestChainDbEnv topLevelConfig chunkInfo $ convertMapKind testInitExtLedger
257263

258264
newtype TestFailure = TestFailure String deriving Show

0 commit comments

Comments
 (0)