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

Commit c61f195

Browse files
committed
[CBR-481/482] Add ObftRoundRobinSpec
1 parent 8964772 commit c61f195

File tree

5 files changed

+122
-34
lines changed

5 files changed

+122
-34
lines changed

chain/cardano-sl-chain.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ 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
258+
Test.Pos.Chain.Lrc.StakeAndHolder
257259
Test.Pos.Chain.Ssc.Arbitrary
258260
Test.Pos.Chain.Ssc.Bi
259261
Test.Pos.Chain.Ssc.CborSpec

chain/test/Test/Pos/Chain/Lrc/FtsSpec.hs

+5-34
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,19 @@ module Test.Pos.Chain.Lrc.FtsSpec
1010

1111
import Universum
1212

13-
import Data.List (scanl1)
14-
import qualified Data.Set as S (deleteFindMin, fromList)
1513
import Test.Hspec (Spec, describe)
1614
import Test.Hspec.QuickCheck (modifyMaxSuccess, prop)
17-
import Test.QuickCheck (Arbitrary (..), Property, choose,
18-
infiniteListOf, suchThat, (===))
15+
import Test.QuickCheck (Arbitrary (..), Property, infiniteListOf,
16+
(===))
1917

2018
import Pos.Chain.Lrc (followTheSatoshi)
21-
import Pos.Core (Coin, SharedSeed, StakeholderId, StakesList,
22-
addressHash, mkCoin, pcK, sumCoins, unsafeAddCoin,
23-
unsafeIntegerToCoin)
19+
import Pos.Core (Coin, SharedSeed, StakeholderId, addressHash, pcK,
20+
sumCoins, unsafeIntegerToCoin)
2421
import Pos.Crypto (PublicKey)
2522

2623
import Test.Pos.Chain.Genesis.Dummy (dummyEpochSlots,
2724
dummyProtocolConstants)
25+
import Test.Pos.Chain.Lrc.StakeAndHolder (StakeAndHolder (..))
2826
import Test.Pos.Core.Arbitrary ()
2927
import Test.Pos.Util.QuickCheck.Property (qcNotElem)
3028

@@ -70,33 +68,6 @@ spec = do
7068
acceptable present (1 - (1 - highStake) ^ pLen)
7169
&& acceptable chosen highStake
7270

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

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)