Skip to content

Commit afe45d7

Browse files
Use chain hash comparison for properties on parametric blk
Some genesis tests still depend on 'TestBlock' specific logic. Specifically, the `TestHash` (the `HeaderHash TestBlock` type family instance) stores a number list representation of the path on the `BlockTree` leading to the corresponding block (i.e. its prefix `AnchoredFragment`); this is used to implement certain properties. Here, such properties are refactored to parametric blocks by using direct hash comparison (in some cases on the `BlockTree` trunk). tweag/ouroboros-consensus-testing@3e83852 holds a test showcasing a property of a previous refactor: namely that the order of _fork numbers_ in a `TestHash` are not relevant for the properties that depended on it. This alternative refactor (on tweag/ouroboros-consensus-testing@dab5349) intended to preserve existing code structure, which was confusing, and as such was changed in the end in search for clarity.
1 parent d3cc18c commit afe45d7

File tree

8 files changed

+104
-84
lines changed

8 files changed

+104
-84
lines changed

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Setup.hs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
{-# LANGUAGE DerivingStrategies #-}
33
{-# LANGUAGE ExistentialQuantification #-}
44
{-# LANGUAGE FlexibleContexts #-}
5+
{-# LANGUAGE LambdaCase #-}
56
{-# LANGUAGE NamedFieldPuns #-}
67
{-# LANGUAGE RankNTypes #-}
78
{-# LANGUAGE ScopedTypeVariables #-}
89

910
module Test.Consensus.Genesis.Setup
1011
( module Test.Consensus.Genesis.Setup.GenChains
12+
, castHeaderHash
1113
, forAllGenesisTest
14+
, honestImmutableTip
1215
, runGenesisTest
1316
, runGenesisTest'
17+
, selectedHonestChain
1418
) where
1519

1620
import Control.Exception (throw)
@@ -20,7 +24,12 @@ import Control.Monad.Class.MonadAsync
2024
import Control.Monad.IOSim (IOSim, runSimStrictShutdown)
2125
import Control.Tracer (debugTracer, traceWith)
2226
import Data.Maybe (mapMaybe)
23-
import Ouroboros.Consensus.Block.Abstract (ConvertRawHash, Header)
27+
import Ouroboros.Consensus.Block.Abstract
28+
( ChainHash (..)
29+
, ConvertRawHash
30+
, GetHeader
31+
, Header
32+
)
2433
import Ouroboros.Consensus.Block.SupportsDiffusionPipelining
2534
( BlockSupportsDiffusionPipelining
2635
)
@@ -40,10 +49,12 @@ import Ouroboros.Consensus.Storage.LedgerDB.API
4049
)
4150
import Ouroboros.Consensus.Util.Condense
4251
import Ouroboros.Consensus.Util.IOLike (Exception, fromException)
52+
import qualified Ouroboros.Network.AnchoredFragment as AF
4353
import Ouroboros.Network.Driver.Limits
4454
( ProtocolLimitFailure (ExceededTimeLimit)
4555
)
4656
import Ouroboros.Network.Util.ShowProxy
57+
import Test.Consensus.BlockTree (onTrunk)
4758
import Test.Consensus.Genesis.Setup.Classifiers
4859
( Classifiers (..)
4960
, ResultClassifiers (..)
@@ -209,3 +220,25 @@ forAllGenesisTest generator schedulerConfig shrinker mkProperty = idempotentIOPr
209220
e :: Exception e => Maybe e
210221
e = fromException exn
211222
true = property True
223+
224+
-- | The 'StateView.svSelectedChain' produces an 'AnchoredFragment (Header blk)';
225+
-- this function casts this type's hash to its instance, so that it can be used
226+
-- for lookups on a 'BlockTree'.
227+
castHeaderHash :: ChainHash (Header blk) -> ChainHash blk
228+
castHeaderHash = \case
229+
BlockHash hash -> BlockHash hash
230+
GenesisHash -> GenesisHash
231+
232+
-- | Check if the immutable tip of the selected chain of a 'GenesisTest' is honest.
233+
-- In this setting, the immutable tip corresponds to the selected chain anchor
234+
-- (see 'Ouroboros.Consensus.Storage.ChainDB.API.getCurrentChain') and
235+
-- the honest chain is represented by the test 'BlockTree' trunk.
236+
honestImmutableTip :: GetHeader blk => GenesisTestFull blk -> StateView blk -> Bool
237+
honestImmutableTip GenesisTest{gtBlockTree} StateView{svSelectedChain} =
238+
onTrunk gtBlockTree $ AF.anchorPoint svSelectedChain
239+
240+
-- | Check if the tip of the selected chain of a 'GenesisTest' is honest.
241+
-- In this setting, the honest chain corresponds to the test 'BlockTree' trunk.
242+
selectedHonestChain :: GetHeader blk => GenesisTestFull blk -> StateView blk -> Bool
243+
selectedHonestChain GenesisTest{gtBlockTree} StateView{svSelectedChain} =
244+
onTrunk gtBlockTree $ AF.headPoint $ svSelectedChain

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Setup/Classifiers.hs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
{-# LANGUAGE ScopedTypeVariables #-}
44
{-# LANGUAGE TupleSections #-}
55
{-# LANGUAGE TypeFamilies #-}
6-
{-# LANGUAGE TypeOperators #-}
76

87
module Test.Consensus.Genesis.Setup.Classifiers
98
( Classifiers (..)
@@ -12,20 +11,15 @@ module Test.Consensus.Genesis.Setup.Classifiers
1211
, classifiers
1312
, resultClassifiers
1413
, scheduleClassifiers
15-
, simpleHash
1614
) where
1715

1816
import Cardano.Ledger.BaseTypes (unNonZero)
1917
import Cardano.Slotting.Slot (WithOrigin (..))
2018
import Data.List (sortOn, tails)
21-
import qualified Data.List.NonEmpty as NonEmpty
2219
import qualified Data.Map as Map
2320
import Data.Maybe (mapMaybe)
24-
import Data.Word (Word64)
2521
import Ouroboros.Consensus.Block.Abstract
26-
( ChainHash (..)
27-
, HasHeader
28-
, HeaderHash
22+
( HasHeader
2923
, SlotNo (SlotNo)
3024
, blockSlot
3125
, succWithOrigin
@@ -60,7 +54,6 @@ import Test.Consensus.PointSchedule
6054
import Test.Consensus.PointSchedule.Peers (PeerId (..), Peers (..))
6155
import Test.Consensus.PointSchedule.SinglePeer (SchedulePoint (..))
6256
import Test.Util.Orphans.IOLike ()
63-
import Test.Util.TestBlock (TestHash (TestHash))
6457

6558
-- | Interesting categories to classify test inputs
6659
data Classifiers
@@ -284,11 +277,3 @@ scheduleClassifiers GenesisTest{gtSchedule = schedule, gtBlockTree} =
284277
(t0, _) : points -> all ((== t0) . fst) points
285278

286279
allAdversariesTrivial = all id $ adversarialPeers $ isTrivial <$> psSchedule schedule
287-
288-
simpleHash ::
289-
HeaderHash block ~ TestHash =>
290-
ChainHash block ->
291-
[Word64]
292-
simpleHash = \case
293-
BlockHash (TestHash h) -> reverse (NonEmpty.toList h)
294-
GenesisHash -> []

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Tests/DensityDisconnect.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# LANGUAGE DuplicateRecordFields #-}
22
{-# LANGUAGE LambdaCase #-}
33
{-# LANGUAGE NamedFieldPuns #-}
4+
{-# LANGUAGE TypeApplications #-}
45
{-# LANGUAGE TypeFamilies #-}
56
{-# LANGUAGE ViewPatterns #-}
67

@@ -514,7 +515,7 @@ prop_densityDisconnectMonotonic =
514515
-- it gets disconnected and then the selection progresses.
515516
prop_densityDisconnectTriggersChainSel :: Property
516517
prop_densityDisconnectTriggersChainSel =
517-
forAllGenesisTest
518+
forAllGenesisTest @TestBlock
518519
( do
519520
gt@GenesisTest{gtBlockTree} <- genChains (pure 1)
520521
let ps = lowDensitySchedule gtBlockTree

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Tests/LoE.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE DerivingStrategies #-}
33
{-# LANGUAGE NamedFieldPuns #-}
44
{-# LANGUAGE ScopedTypeVariables #-}
5+
{-# LANGUAGE TypeApplications #-}
56
{-# LANGUAGE ViewPatterns #-}
67

78
module Test.Consensus.Genesis.Tests.LoE (tests) where
@@ -33,6 +34,7 @@ import Test.Tasty
3334
import Test.Tasty.QuickCheck
3435
import Test.Util.Orphans.IOLike ()
3536
import Test.Util.PartialAccessors
37+
import Test.Util.TestBlock (TestBlock)
3638
import Test.Util.TestEnv
3739
( adjustQuickCheckMaxSize
3840
, adjustQuickCheckTests
@@ -66,7 +68,7 @@ prop_adversaryHitsTimeouts timeoutsEnabled =
6668
-- at the end of the test for the adversaries to get disconnected, by adding an extra point.
6769
-- If this point gets removed by the shrinker, we lose that property and the test becomes useless.
6870
noShrinking $
69-
forAllGenesisTest
71+
forAllGenesisTest @TestBlock
7072
( do
7173
gt@GenesisTest{gtBlockTree} <- genChains (pure 1)
7274
let ps = delaySchedule gtBlockTree

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Tests/LoP.hs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE DerivingStrategies #-}
33
{-# LANGUAGE NamedFieldPuns #-}
44
{-# LANGUAGE ScopedTypeVariables #-}
5+
{-# LANGUAGE TypeApplications #-}
56
{-# LANGUAGE ViewPatterns #-}
67

78
module Test.Consensus.Genesis.Tests.LoP (tests) where
@@ -45,6 +46,7 @@ import Test.Tasty
4546
import Test.Tasty.QuickCheck
4647
import Test.Util.Orphans.IOLike ()
4748
import Test.Util.PartialAccessors
49+
import Test.Util.TestBlock (TestBlock)
4850
import Test.Util.TestEnv
4951
( adjustQuickCheckMaxSize
5052
, adjustQuickCheckTests
@@ -84,7 +86,7 @@ tests =
8486
-- no exception in the ChainSync client.
8587
prop_wait :: Bool -> Property
8688
prop_wait mustTimeout =
87-
forAllGenesisTest
89+
forAllGenesisTest @TestBlock
8890
( do
8991
gt@GenesisTest{gtBlockTree} <- genChains (pure 0)
9092
let ps = dullSchedule 10 (btTrunk gtBlockTree)
@@ -122,7 +124,7 @@ prop_wait mustTimeout =
122124
-- no exception.
123125
prop_waitBehindForecastHorizon :: Property
124126
prop_waitBehindForecastHorizon =
125-
forAllGenesisTest
127+
forAllGenesisTest @TestBlock
126128
( do
127129
gt@GenesisTest{gtBlockTree} <- genChains (pure 0)
128130
let ps = dullSchedule (btTrunk gtBlockTree)
@@ -170,7 +172,7 @@ prop_waitBehindForecastHorizon =
170172
-- serve the @n@th block, barely.
171173
prop_serve :: Bool -> Property
172174
prop_serve mustTimeout =
173-
forAllGenesisTest
175+
forAllGenesisTest @TestBlock
174176
( do
175177
gt@GenesisTest{gtBlockTree} <- genChains (pure 0)
176178
let lbpRate = borderlineRate (AF.length (btTrunk gtBlockTree))
@@ -228,7 +230,7 @@ prop_delayAttack lopEnabled =
228230
-- at the end of the test for the adversaries to get disconnected, by adding an extra point.
229231
-- If this point gets removed by the shrinker, we lose that property and the test becomes useless.
230232
noShrinking $
231-
forAllGenesisTest
233+
forAllGenesisTest @TestBlock
232234
( do
233235
gt@GenesisTest{gtBlockTree} <- genChains (pure 1)
234236
let gt' = gt{gtLoPBucketParams = LoPBucketParams{lbpCapacity = 10, lbpRate = 1}}

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Tests/LongRangeAttack.hs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
{-# LANGUAGE BlockArguments #-}
22
{-# LANGUAGE DerivingStrategies #-}
3+
{-# LANGUAGE FlexibleContexts #-}
34
{-# LANGUAGE NamedFieldPuns #-}
45
{-# LANGUAGE ScopedTypeVariables #-}
6+
{-# LANGUAGE TypeApplications #-}
57

68
module Test.Consensus.Genesis.Tests.LongRangeAttack (tests) where
79

810
import Data.Functor (($>))
9-
import Ouroboros.Consensus.Block.Abstract (Header, HeaderHash)
10-
import Ouroboros.Network.AnchoredFragment (headAnchor)
11-
import qualified Ouroboros.Network.AnchoredFragment as AF
1211
import Test.Consensus.Genesis.Setup
1312
import Test.Consensus.Genesis.Setup.Classifiers
1413
( allAdversariesForecastable
1514
, allAdversariesSelectable
1615
, classifiers
1716
)
1817
import Test.Consensus.PeerSimulator.Run (defaultSchedulerConfig)
19-
import Test.Consensus.PeerSimulator.StateView
2018
import Test.Consensus.PointSchedule
2119
import Test.Consensus.PointSchedule.Shrinking (shrinkPeerSchedules)
2220
import Test.Tasty
2321
import Test.Tasty.QuickCheck
2422
import Test.Util.Orphans.IOLike ()
25-
import Test.Util.TestBlock (TestBlock, unTestHash)
23+
import Test.Util.TestBlock (TestBlock)
2624
import Test.Util.TestEnv (adjustQuickCheckTests)
2725

2826
tests :: TestTree
@@ -49,7 +47,7 @@ prop_longRangeAttack =
4947
-- honest node to win. Hence the `noShrinking`.
5048

5149
noShrinking $
52-
forAllGenesisTest
50+
forAllGenesisTest @TestBlock
5351
( do
5452
-- Create a block tree with @1@ alternative chain.
5553
gt@GenesisTest{gtBlockTree} <- genChains (pure 1)
@@ -64,13 +62,5 @@ prop_longRangeAttack =
6462
shrinkPeerSchedules
6563
-- NOTE: This is the expected behaviour of Praos to be reversed with
6664
-- Genesis. But we are testing Praos for the moment. Do not forget to remove
67-
-- `noShrinking` above when removing this negation.
68-
(\_ -> not . isHonestTestFragH . svSelectedChain)
69-
where
70-
isHonestTestFragH :: AF.AnchoredFragment (Header TestBlock) -> Bool
71-
isHonestTestFragH frag = case headAnchor frag of
72-
AF.AnchorGenesis -> True
73-
AF.Anchor _ hash _ -> isHonestTestHeaderHash hash
74-
75-
isHonestTestHeaderHash :: HeaderHash TestBlock -> Bool
76-
isHonestTestHeaderHash = all (0 ==) . unTestHash
65+
-- 'noShrinking' above when removing this negation.
66+
(\genesisTest -> not . selectedHonestChain genesisTest)

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/Genesis/Tests/Uniform.hs

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE LambdaCase #-}
55
{-# LANGUAGE NamedFieldPuns #-}
66
{-# LANGUAGE ScopedTypeVariables #-}
7+
{-# LANGUAGE TypeApplications #-}
78
{-# LANGUAGE TypeFamilies #-}
89

910
-- | Peer simulator tests based on randomly generated schedules. They share the
@@ -26,7 +27,11 @@ import qualified Data.Map.Strict as Map
2627
import Data.Maybe (fromMaybe, mapMaybe)
2728
import Data.Word (Word64)
2829
import GHC.Stack (HasCallStack)
29-
import Ouroboros.Consensus.Block.Abstract (WithOrigin (NotOrigin))
30+
import Ouroboros.Consensus.Block.Abstract
31+
( ChainHash (..)
32+
, GetHeader
33+
, WithOrigin (NotOrigin)
34+
)
3035
import Ouroboros.Consensus.Util.Condense (condense)
3136
import qualified Ouroboros.Network.AnchoredFragment as AF
3237
import Ouroboros.Network.Block (blockNo, blockSlot, unBlockNo)
@@ -93,13 +98,10 @@ tests =
9398
-- * no honest peer has been disconnected,
9499
-- * the immutable tip is on the best chain, and
95100
-- * the immutable tip is no older than s + d + 1 slots
96-
theProperty ::
97-
GenesisTestFull TestBlock ->
98-
StateView TestBlock ->
99-
Property
101+
theProperty :: (AF.HasHeader blk, GetHeader blk) => GenesisTestFull blk -> StateView blk -> Property
100102
theProperty genesisTest stateView@StateView{svSelectedChain} =
101103
classify genesisWindowAfterIntersection "Full genesis window after intersection" $
102-
classify (isOrigin immutableTipHash) "Immutable tip is Origin" $
104+
classify (immutableTipHash == GenesisHash) "Immutable tip is Origin" $
103105
label disconnectedLabel $
104106
classify (advCount < length (btBranches gtBlockTree)) "Some adversaries performed rollbacks" $
105107
counterexample killedPeers $
@@ -109,7 +111,7 @@ theProperty genesisTest stateView@StateView{svSelectedChain} =
109111
conjoin
110112
[ counterexample "Honest peers shouldn't be disconnected" (not $ any isHonestPeerId disconnected)
111113
, counterexample ("The immutable tip should be honest: " ++ show immutableTip) $
112-
property (isHonest immutableTipHash)
114+
honestImmutableTip genesisTest stateView
113115
, immutableTipIsRecent
114116
]
115117
where
@@ -124,11 +126,7 @@ theProperty genesisTest stateView@StateView{svSelectedChain} =
124126
(At h, Origin) -> h
125127
_ -> 0
126128

127-
isOrigin = null
128-
129-
isHonest = all (0 ==)
130-
131-
immutableTipHash = simpleHash (AF.anchorToHash immutableTip)
129+
immutableTipHash = AF.anchorToHash immutableTip
132130

133131
immutableTip = AF.anchor svSelectedChain
134132

@@ -370,22 +368,20 @@ prop_loeStalling =
370368
shrinkPeerSchedules
371369
prop
372370
where
373-
prop GenesisTest{gtBlockTree = BlockTree{btTrunk, btBranches}} StateView{svSelectedChain} =
371+
prop ::
372+
forall blk. (AF.HasHeader blk, GetHeader blk) => GenesisTestFull blk -> StateView blk -> Property
373+
prop gt@GenesisTest{gtBlockTree = BlockTree{btTrunk, btBranches}} sv@StateView{svSelectedChain} =
374374
classify (any (== selectionTip) allTips) "The selection is at a branch tip" $
375375
classify (any anchorIsImmutableTip suffixes) "The immutable tip is at a fork intersection" $
376-
property (isHonest immutableTipHash)
376+
honestImmutableTip gt sv
377377
where
378-
anchorIsImmutableTip branch = simpleHash (AF.anchorToHash (AF.anchor branch)) == immutableTipHash
378+
anchorIsImmutableTip branch = AF.anchorToHash (AF.anchor branch) == immutableTipHash
379379

380-
isHonest = all (0 ==)
380+
immutableTipHash = castHeaderHash . AF.anchorToHash $ AF.anchor svSelectedChain
381381

382-
immutableTipHash = simpleHash (AF.anchorToHash immutableTip)
382+
selectionTip = castHeaderHash $ AF.headHash svSelectedChain
383383

384-
immutableTip = AF.anchor svSelectedChain
385-
386-
selectionTip = simpleHash (AF.headHash svSelectedChain)
387-
388-
allTips = simpleHash . AF.headHash <$> (btTrunk : suffixes)
384+
allTips = AF.headHash <$> (btTrunk : suffixes)
389385

390386
suffixes = btbSuffix <$> btBranches
391387

@@ -395,7 +391,7 @@ prop_loeStalling =
395391
-- This ensures that a user may shut down their machine while syncing without additional vulnerabilities.
396392
prop_downtime :: Property
397393
prop_downtime =
398-
forAllGenesisTest
394+
forAllGenesisTest @TestBlock
399395
( genChains (QC.choose (1, 4)) `enrichedWith` \gt ->
400396
ensureScheduleDuration gt <$> stToGen (uniformPoints (pointsGeneratorParams gt) (gtBlockTree gt))
401397
)

0 commit comments

Comments
 (0)