Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 06c67bd

Browse files
committed
[CBR-481/482] Add ObftRoundRobinSpec
1 parent 7aba046 commit 06c67bd

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

chain/cardano-sl-chain.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ test-suite chain-test
254254
Test.Pos.Chain.Genesis.Gen
255255
Test.Pos.Chain.Genesis.Json
256256
Test.Pos.Chain.Lrc.FtsSpec
257+
Test.Pos.Chain.Lrc.ObftRoundRobinSpec
257258
Test.Pos.Chain.Ssc.Arbitrary
258259
Test.Pos.Chain.Ssc.Bi
259260
Test.Pos.Chain.Ssc.CborSpec
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{-# LANGUAGE ViewPatterns #-}
2+
3+
-- | Specification of Pos.Chain.Lrc.OBFT (which is basically a pure
4+
-- version of 'Pos.DB.Lrc.OBFT').
5+
6+
module Test.Pos.Chain.Lrc.ObftRoundRobinSpec
7+
( spec
8+
) where
9+
10+
import Universum hiding (sort)
11+
12+
import Data.List (scanl1)
13+
import Data.List.NonEmpty (sort, (!!))
14+
import qualified Data.Set as S (deleteFindMin, fromList)
15+
import Test.Hspec (Spec, describe)
16+
import Test.Hspec.QuickCheck (modifyMaxSuccess, prop)
17+
import Test.QuickCheck (Arbitrary (..), Property, choose, suchThat,
18+
(===))
19+
20+
import Pos.Chain.Lrc (getEpochSlotLeaderScheduleObftPure,
21+
getSlotLeaderObftPure)
22+
import Pos.Core (EpochIndex, SlotCount, SlotId, StakesList,
23+
addressHash, flattenEpochOrSlot, mkCoin, unsafeAddCoin)
24+
import Pos.Crypto (PublicKey)
25+
26+
import Test.Pos.Core.Arbitrary (genPositiveSlotCount)
27+
28+
spec :: Spec
29+
spec = do
30+
describe "Pos.Chain.Lrc.OBFT" $ do
31+
describe "Round-robin" $ do
32+
modifyMaxSuccess (const 10000) $ do
33+
prop description_rrListLength
34+
(rrListLength <$> genPositiveSlotCount)
35+
prop description_rrCorrectSlotLeader
36+
(rrCorrectSlotLeader <$> genPositiveSlotCount)
37+
where
38+
description_rrListLength =
39+
"the amount of stakeholders is the same as the number of slots in an epoch"
40+
description_rrCorrectSlotLeader =
41+
"the correct slot leader is chosen given any epoch and slot"
42+
43+
-- | Type used to generate random stakes and a 'PublicKey' that
44+
-- doesn't have any stake.
45+
--
46+
-- Two necessarily different public keys are generated, as well as a list of
47+
-- public keys who will be our other stakeholders. To guarantee a non-empty
48+
-- stakes map, one of these public keys is inserted in the list, which is
49+
-- converted to a set and then to a map.
50+
newtype StakeAndHolder = StakeAndHolder
51+
{ getNoStake :: (PublicKey, StakesList)
52+
} deriving Show
53+
54+
instance Arbitrary StakeAndHolder where
55+
arbitrary = StakeAndHolder <$> do
56+
pk1 <- arbitrary
57+
pk2 <- arbitrary `suchThat` ((/=) pk1)
58+
listPks <- do
59+
n <- choose (2, 10)
60+
replicateM n arbitrary
61+
coins <- mkCoin <$> choose (1, 1000)
62+
let setPks :: Set PublicKey
63+
setPks = S.fromList $ pk1 : pk2 : listPks
64+
(myPk, restPks) = S.deleteFindMin setPks
65+
nRest = length restPks
66+
values = scanl1 unsafeAddCoin $ replicate nRest coins
67+
stakesList = map addressHash (toList restPks) `zip` values
68+
return (myPk, stakesList)
69+
70+
rrListLength
71+
:: SlotCount
72+
-> EpochIndex
73+
-> StakeAndHolder
74+
-> Property
75+
rrListLength epochSlotCount epochIndex (getNoStake -> (_, stakes)) = do
76+
length (getEpochSlotLeaderScheduleObftPure epochIndex epochSlotCount stakeholders)
77+
=== fromIntegral epochSlotCount
78+
where
79+
stakeholders = case nonEmpty (map fst stakes) of
80+
Just s -> s
81+
Nothing -> error "rrListLength: Empty list of stakeholders"
82+
83+
rrCorrectSlotLeader
84+
:: SlotCount
85+
-> SlotId
86+
-> StakeAndHolder
87+
-> Property
88+
rrCorrectSlotLeader epochSlotCount slotId (getNoStake -> (_, stakes)) = do
89+
actualSlotLeader === expectedSlotLeader
90+
where
91+
stakeholders = case nonEmpty (map fst stakes) of
92+
Just s -> s
93+
Nothing -> error "rrCorrectSlotLeader: Empty list of stakeholders"
94+
flatSlotId = flattenEpochOrSlot epochSlotCount slotId
95+
expectedSlotLeaderIndex =
96+
(fromIntegral flatSlotId :: Int) `mod` (length stakeholders)
97+
expectedSlotLeader = (sort stakeholders) !! expectedSlotLeaderIndex
98+
actualSlotLeader = getSlotLeaderObftPure slotId epochSlotCount stakeholders

core/test/Test/Pos/Core/Arbitrary.hs

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module Test.Pos.Core.Arbitrary
2020
, UnreasonableEoS (..)
2121

2222
, genAddress
23+
, genPositiveSlotCount
2324
, genSlotId
2425
, genLocalSlotIndex
2526
) where
@@ -99,6 +100,12 @@ deriving instance Arbitrary ChainDifficulty
99100

100101
deriving instance Arbitrary SlotCount
101102

103+
genPositiveSlotCount :: Gen SlotCount
104+
genPositiveSlotCount = do
105+
let upperBound = 5000 -- no specific reason for using 5000
106+
x <- choose (1, upperBound)
107+
pure $ SlotCount x
108+
102109
maxReasonableEpoch :: Integral a => a
103110
maxReasonableEpoch = 5 * 1000 * 1000 * 1000 * 1000 -- 5 * 10^12, because why not
104111

0 commit comments

Comments
 (0)