Skip to content

Commit 748fcf7

Browse files
Added ledger peer snapshot property tests
Improved naming of functions related to calculating big ledger stake distribution which were to moved public ouroboros-network-api component.
1 parent c4a3839 commit 748fcf7

File tree

6 files changed

+160
-30
lines changed

6 files changed

+160
-30
lines changed

ouroboros-network-api/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
* New type supports CBOR & JSON for serialisation purposes.
1515
* Ledger peer snapshot is versioned in case changes need to be made to the
1616
encoding format in the future.
17+
* Renamed:
18+
* `accBigPoolStake` -> `accumulateBigLedgerStake`
19+
and `reRelativeStake` -> `recomputeRelativeStake`
1720

1821
* Added `Measure` and `BoundedMeasure` instances for `SizeInBytes`.
1922

ouroboros-network-api/src/Ouroboros/Network/PeerSelection/LedgerPeers/Utils.hs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
module Ouroboros.Network.PeerSelection.LedgerPeers.Utils
55
( bigLedgerPeerQuota
6-
, accBigPoolStake
7-
, reRelativeStake
6+
, accumulateBigLedgerStake
7+
, recomputeRelativeStake
88
, AccPoolStake (..)
99
, PoolStake (..)
1010
, RelayAccessPoint (..)
@@ -29,13 +29,13 @@ bigLedgerPeerQuota = 0.9
2929
-- and tag each one with cumulative stake, with a cutoff
3030
-- at 'bigLedgerPeerQuota'
3131
--
32-
accBigPoolStake :: [(PoolStake, NonEmpty RelayAccessPoint)]
33-
-> [(AccPoolStake, (PoolStake, NonEmpty RelayAccessPoint))]
34-
accBigPoolStake =
32+
accumulateBigLedgerStake :: [(PoolStake, NonEmpty RelayAccessPoint)]
33+
-> [(AccPoolStake, (PoolStake, NonEmpty RelayAccessPoint))]
34+
accumulateBigLedgerStake =
3535
takeWhilePrev (\(acc, _) -> acc <= bigLedgerPeerQuota)
3636
. go 0
3737
. sortOn (Down . fst)
38-
. reRelativeStake BigLedgerPeers
38+
. recomputeRelativeStake BigLedgerPeers
3939
where
4040
takeWhilePrev :: (a -> Bool) -> [a] -> [a]
4141
takeWhilePrev f as =
@@ -55,10 +55,10 @@ accBigPoolStake =
5555
-- | Not all stake pools have valid \/ usable relay information. This means that
5656
-- we need to recalculate the relative stake for each pool.
5757
--
58-
reRelativeStake :: LedgerPeersKind
58+
recomputeRelativeStake :: LedgerPeersKind
5959
-> [(PoolStake, NonEmpty RelayAccessPoint)]
6060
-> [(PoolStake, NonEmpty RelayAccessPoint)]
61-
reRelativeStake ledgerPeersKind pl =
61+
recomputeRelativeStake ledgerPeersKind pl =
6262
let pl' = first adjustment <$> pl
6363
total = List.foldl' (+) 0 (fst <$> pl')
6464
pl'' = first (/ total) <$> pl'

ouroboros-network/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
- Fixed block fetch client assertion failure
2323
* Make it build with ghc-9.10
2424
* Implemented provision of big ledger peers from the snapshot by `ledgerPeersThread`
25+
* Added property test checking if `ledgerPeersThread` is providing big ledger peers
26+
from the snapshot when appropriate conditions are met
27+
* Added property tests checking if `LedgerPeerSnapshot` CBOR encoding is valid,
28+
and decode/encode = id, as well as some property tests for calculating big ledger
29+
peers
2530

2631
## 0.16.1.1 -- 2024-06-28
2732

ouroboros-network/sim-tests-lib/Test/Ouroboros/Network/LedgerPeers.hs

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{-# LANGUAGE BangPatterns #-}
22
{-# LANGUAGE DerivingVia #-}
3-
{-# LANGUAGE LambdaCase #-}
43
{-# LANGUAGE NamedFieldPuns #-}
54
{-# LANGUAGE OverloadedStrings #-}
65
{-# LANGUAGE RankNTypes #-}
@@ -41,6 +40,8 @@ import Network.DNS (Domain)
4140
import Cardano.Binary
4241
import Cardano.Slotting.Slot (SlotNo (..), WithOrigin (..))
4342
import Ouroboros.Network.PeerSelection.LedgerPeers
43+
import Ouroboros.Network.PeerSelection.LedgerPeers.Utils
44+
(recomputeRelativeStake)
4445
import Ouroboros.Network.PeerSelection.RelayAccessPoint
4546
import Ouroboros.Network.PeerSelection.RootPeersDNS
4647
import Ouroboros.Network.Testing.Data.Script
@@ -50,11 +51,13 @@ import Test.Tasty
5051
import Test.Tasty.QuickCheck
5152
import Text.Printf
5253

54+
5355
tests :: TestTree
5456
tests = testGroup "Ouroboros.Network.LedgerPeers"
5557
[ testProperty "Pick 100%" prop_pick100
5658
, testProperty "Pick" prop_pick
57-
, testProperty "accBigPoolStake" prop_accBigPoolStake
59+
, testProperty "accumulateBigLedgerStake" prop_accumulateBigLedgerStake
60+
, testProperty "recomputeRelativeStake" prop_recomputeRelativeStake
5861
, testProperty "getLedgerPeers invariants" prop_getLedgerPeers
5962
, testProperty "LedgerPeerSnapshot CBOR version 1" prop_ledgerPeerSnapshotCBORV1
6063
, testProperty "LedgerPeerSnapshot JSON version 1" prop_ledgerPeerSnapshotJSONV1
@@ -105,7 +108,7 @@ newtype ArbitrarySlotNo =
105108
-- of the tests we run.
106109
instance Arbitrary ArbitrarySlotNo where
107110
arbitrary =
108-
ArbitrarySlotNo . SlotNo <$> arbitrarySizedBoundedIntegral
111+
ArbitrarySlotNo . SlotNo <$> arbitrary
109112

110113
data StakePool = StakePool {
111114
spStake :: !Word64
@@ -163,6 +166,93 @@ instance Arbitrary ArbLedgerPeersKind where
163166
shrink (ArbLedgerPeersKind AllLedgerPeers) = [ArbLedgerPeersKind BigLedgerPeers]
164167
shrink (ArbLedgerPeersKind BigLedgerPeers) = []
165168

169+
newtype ArbStakeMapOverSource = ArbStakeMapOverSource { getArbStakeMapOverSource :: StakeMapOverSource }
170+
deriving Show
171+
172+
instance Arbitrary ArbStakeMapOverSource where
173+
arbitrary = do
174+
peerSnapshot <-
175+
oneof [ pure Nothing, Just <$> genPeerSnapshot ]
176+
ledgerWithOrigin <- genWithOrigin
177+
ula <- arbitrary
178+
ledgerPeers <-
179+
case (ula, ledgerWithOrigin) of
180+
(Always, _) -> LedgerPeers TooOld . getLedgerPools <$> arbitrary
181+
(After slotNo, Origin) | slotNo > 0 -> return BeforeSlot
182+
(After afterSlotNo, At atSlotNo)
183+
| afterSlotNo <= atSlotNo -> LedgerPeers TooOld . getLedgerPools <$> arbitrary
184+
_otherwise -> return BeforeSlot
185+
(peerMap, bigPeerMap, cachedSlot) <-
186+
return $ case peerSnapshot of
187+
Nothing -> (Map.empty, Map.empty, Nothing)
188+
Just (LedgerPeerSnapshotV1 (At slot, accPools))
189+
-> (Map.fromList accPools, Map.fromList accPools, Just slot)
190+
_otherwise -> error "impossible!"
191+
return $ ArbStakeMapOverSource StakeMapOverSource {
192+
ledgerWithOrigin,
193+
ledgerPeers,
194+
peerSnapshot,
195+
peerMap,
196+
bigPeerMap,
197+
ula,
198+
cachedSlot }
199+
where
200+
genWithOrigin = do
201+
ArbitrarySlotNo slotNo <- arbitrary
202+
return $ if slotNo == 0 then Origin else At slotNo
203+
genPeerSnapshot = do
204+
slotNo <- At . getPositive <$> arbitrary
205+
pools <- accumulateBigLedgerStake . getLedgerPools <$> arbitrary
206+
return $ LedgerPeerSnapshotV1 (slotNo, pools)
207+
208+
-- | This test checks whether requesting ledger peers works as intended
209+
-- when snapshot data is available. For each request, peers must be returned from the right
210+
-- source - either the ledger or snapshot, depending on whether which source is fresher.
211+
--
212+
prop_ledgerPeerSnapshot_requests :: ArbStakeMapOverSource
213+
-> Property
214+
prop_ledgerPeerSnapshot_requests ArbStakeMapOverSource {
215+
getArbStakeMapOverSource = params@StakeMapOverSource {
216+
ledgerWithOrigin,
217+
ledgerPeers,
218+
peerSnapshot,
219+
ula } } =
220+
counterexample (unlines
221+
["Counterexample:", "Ledger slot " ++ show ledgerWithOrigin,
222+
"Ledger pools: " ++ show ledgerPeers,
223+
"Snapshot? :" ++ show peerSnapshot,
224+
"UseLedgerAfter: " ++ show ula]) $
225+
let (poolMap, bigPoolMap, _slot) = stakeMapWithSlotOverSource params
226+
bigPoolRelays = fmap (snd . snd) . Map.toList $ bigPoolMap
227+
poolRelays = fmap (snd . snd) . Map.toList $ poolMap
228+
in case (ledgerWithOrigin, ledgerPeers, peerSnapshot) of
229+
(At t, LedgerPeers _ ledgerPools, Just (LedgerPeerSnapshot (At t', snapshotAccStake)))
230+
| t' >= t ->
231+
snapshotRelays === bigPoolRelays .&&. bigPoolRelays === poolRelays
232+
| otherwise ->
233+
bigPoolRelays === ledgerBigPoolRelays
234+
.&&. poolRelays === ledgerRelays
235+
where
236+
snapshotRelays = fmap (snd . snd) snapshotAccStake
237+
ledgerBigPoolRelays = fmap (snd . snd) (accumulateBigLedgerStake ledgerPools)
238+
ledgerRelays = fmap (snd . snd) . Map.toList $ accPoolStake ledgerPools
239+
240+
(_, LedgerPeers _ ledgerPools, Nothing) ->
241+
bigPoolRelays === ledgerBigPoolRelays
242+
.&&. poolRelays === ledgerRelays
243+
where
244+
ledgerBigPoolRelays = fmap (snd . snd) (accumulateBigLedgerStake ledgerPools)
245+
ledgerRelays = fmap (snd . snd) . Map.toList $ accPoolStake ledgerPools
246+
247+
(_, _, Just (LedgerPeerSnapshot (At t', snapshotAccStake)))
248+
| After slot <- ula, t' >= slot ->
249+
snapshotRelays === bigPoolRelays .&&. bigPoolRelays === poolRelays
250+
where
251+
snapshotRelays = fmap (snd . snd) snapshotAccStake
252+
253+
_otherwise -> bigPoolRelays === [] .&&. poolRelays === []
254+
255+
166256
-- | A pool with 100% stake should always be picked.
167257
prop_pick100 :: Word16
168258
-> NonNegative Int -- ^ number of pools with 0 stake
@@ -317,10 +407,9 @@ prop_pick (LedgerPools lps) (ArbLedgerPeersKind ledgerPeersKind) count seed (Moc
317407
=== fromIntegral count `min` numOfPeers)
318408

319409

320-
prop_accBigPoolStake :: LedgerPools -> Property
321-
prop_accBigPoolStake (LedgerPools []) = property True
322-
prop_accBigPoolStake (LedgerPools lps@(_:_)) =
323-
410+
prop_accumulateBigLedgerStake :: LedgerPools -> Property
411+
prop_accumulateBigLedgerStake (LedgerPools []) = property True
412+
prop_accumulateBigLedgerStake (LedgerPools lps@(_:_)) =
324413
-- the accumulated map is non empty, whenever ledger peers set is non
325414
-- empty
326415
not (Map.null accumulatedStakeMap)
@@ -334,7 +423,7 @@ prop_accBigPoolStake (LedgerPools lps@(_:_)) =
334423
>= unAccPoolStake bigLedgerPeerQuota)
335424

336425
-- This property checks that elements of
337-
-- `accBigPoolStake` form an initial sub-list of the ordered ledger
426+
-- `accBigPoolStakeMap` form an initial sub-list of the ordered ledger
338427
-- peers by stake (from large to small).
339428
--
340429
-- We relay on the fact that `Map.elems` returns a list of elements
@@ -346,6 +435,37 @@ prop_accBigPoolStake (LedgerPools lps@(_:_)) =
346435
where
347436
accumulatedStakeMap = accBigPoolStakeMap lps
348437

438+
-- |This functions checks the following properties:
439+
-- 1. The accumulated relative stake adds up to unity
440+
-- 2. No pool relative stake can be less than 0
441+
-- 3. The relays aren't mangled
442+
-- 4. Running this function multiple times always produces the same result
443+
--
444+
prop_recomputeRelativeStake :: LedgerPools -> Property
445+
prop_recomputeRelativeStake (LedgerPools []) = property True
446+
prop_recomputeRelativeStake (LedgerPools lps) = property $ do
447+
lpk <- genLedgerPeersKind
448+
let (accStake, relayAccessPointsUnchangedNonNegativeStake) = go (reStake lpk) lps (0, True)
449+
return $ counterexample "recomputeRelativeStake: relays modified or negative pool stake calculated"
450+
relayAccessPointsUnchangedNonNegativeStake
451+
.&&. accStake === 1
452+
.&&. counterexample "violates idempotency"
453+
((recomputeRelativeStake BigLedgerPeers . recomputeRelativeStake BigLedgerPeers $ lps) == recomputeRelativeStake BigLedgerPeers lps)
454+
where
455+
genLedgerPeersKind = elements [AllLedgerPeers, BigLedgerPeers]
456+
reStake lpk = recomputeRelativeStake lpk lps
457+
-- compare relay access points in both lists for equality
458+
-- where we assume that recomputerelativestake doesn't change
459+
-- the order, and sum up relative stake to make sure it adds up to 1
460+
go ((normPoolStake, raps):rest) ((_, raps'):rest') (accStake, _) =
461+
if raps == raps' && normPoolStake >= 0
462+
then go rest rest' (accStake + normPoolStake, True)
463+
else (accStake + normPoolStake, False)
464+
go [] (_:_) (accStake, _) = (accStake, False)
465+
go (_:_) [] (accStake, _) = (accStake, False)
466+
go _ _ (accStake, relayAccessPointsUnchangedNonNegativeStake) = (accStake, relayAccessPointsUnchangedNonNegativeStake)
467+
468+
349469
prop_getLedgerPeers :: ArbitrarySlotNo
350470
-> ArbitraryLedgerStateJudgement
351471
-> LedgerPools

ouroboros-network/src/Ouroboros/Network/Diffusion/P2P.hs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,11 @@ nullTracers =
245245
data ArgumentsExtra m = ArgumentsExtra {
246246
-- | selection targets for the peer governor
247247
--
248-
daPeerSelectionTargets :: PeerSelectionTargets
248+
daPeerSelectionTargets :: PeerSelectionTargets
249249

250-
, daReadLocalRootPeers :: STM m (LocalRootPeers.Config RelayAccessPoint)
251-
, daReadPublicRootPeers :: STM m (Map RelayAccessPoint PeerAdvertise)
252-
, daReadUseBootstrapPeers :: STM m UseBootstrapPeers
250+
, daReadLocalRootPeers :: STM m (LocalRootPeers.Config RelayAccessPoint)
251+
, daReadPublicRootPeers :: STM m (Map RelayAccessPoint PeerAdvertise)
252+
, daReadUseBootstrapPeers :: STM m UseBootstrapPeers
253253
-- | Depending on configuration, node may provide us with
254254
-- a snapshot of big ledger peers taken at some slot on the chain.
255255
-- These peers may be selected by ledgerPeersThread when requested
@@ -260,8 +260,8 @@ data ArgumentsExtra m = ArgumentsExtra {
260260
-- | Peer's own PeerSharing value.
261261
--
262262
-- This value comes from the node's configuration file and is static.
263-
, daOwnPeerSharing :: PeerSharing
264-
, daReadUseLedgerPeers :: STM m UseLedgerPeers
263+
, daOwnPeerSharing :: PeerSharing
264+
, daReadUseLedgerPeers :: STM m UseLedgerPeers
265265

266266
-- | Timeout which starts once all responder protocols are idle. If the
267267
-- responders stay idle for duration of the timeout, the connection will
@@ -272,7 +272,7 @@ data ArgumentsExtra m = ArgumentsExtra {
272272
--
273273
-- See 'serverProtocolIdleTimeout'.
274274
--
275-
, daProtocolIdleTimeout :: DiffTime
275+
, daProtocolIdleTimeout :: DiffTime
276276

277277
-- | Time for which /node-to-node/ connections are kept in
278278
-- 'TerminatingState', it should correspond to the OS configured @TCP@
@@ -282,21 +282,21 @@ data ArgumentsExtra m = ArgumentsExtra {
282282
-- purpose is to be resilient for delayed packets in the same way @TCP@
283283
-- is using @TIME_WAIT@.
284284
--
285-
, daTimeWaitTimeout :: DiffTime
285+
, daTimeWaitTimeout :: DiffTime
286286

287287
-- | Churn interval between churn events in deadline mode. A small fuzz
288288
-- is added (max 10 minutes) so that not all nodes churn at the same time.
289289
--
290290
-- By default it is set to 3300 seconds.
291291
--
292-
, daDeadlineChurnInterval :: DiffTime
292+
, daDeadlineChurnInterval :: DiffTime
293293

294294
-- | Churn interval between churn events in bulk sync mode. A small fuzz
295295
-- is added (max 1 minute) so that not all nodes churn at the same time.
296296
--
297297
-- By default it is set to 300 seconds.
298298
--
299-
, daBulkChurnInterval :: DiffTime
299+
, daBulkChurnInterval :: DiffTime
300300
}
301301

302302
--

ouroboros-network/src/Ouroboros/Network/PeerSelection/LedgerPeers.hs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module Ouroboros.Network.PeerSelection.LedgerPeers
2626
, StakeMapOverSource (..)
2727
-- * Ledger Peers specific functions
2828
, accPoolStake
29+
, accumulateBigLedgerStake
2930
, accBigPoolStakeMap
3031
, bigLedgerPeerQuota
3132
, stakeMapWithSlotOverSource
@@ -63,8 +64,9 @@ import Data.Word (Word16, Word64)
6364
import Network.DNS qualified as DNS
6465
import Ouroboros.Network.PeerSelection.LedgerPeers.Common
6566
import Ouroboros.Network.PeerSelection.LedgerPeers.Type
66-
import Ouroboros.Network.PeerSelection.LedgerPeers.Utils (accBigPoolStake,
67-
bigLedgerPeerQuota, reRelativeStake)
67+
import Ouroboros.Network.PeerSelection.LedgerPeers.Utils
68+
(accumulateBigLedgerStake, bigLedgerPeerQuota,
69+
recomputeRelativeStake)
6870
import Ouroboros.Network.PeerSelection.RelayAccessPoint
6971
import Ouroboros.Network.PeerSelection.RootPeersDNS
7072
import Ouroboros.Network.PeerSelection.RootPeersDNS.LedgerPeers
@@ -114,7 +116,7 @@ accPoolStake :: [(PoolStake, NonEmpty RelayAccessPoint)]
114116
accPoolStake =
115117
Map.fromList
116118
. List.foldl' fn []
117-
. reRelativeStake AllLedgerPeers
119+
. recomputeRelativeStake AllLedgerPeers
118120
where
119121
fn :: [(AccPoolStake, (PoolStake, NonEmpty RelayAccessPoint))]
120122
-> (PoolStake, NonEmpty RelayAccessPoint)
@@ -133,7 +135,7 @@ accBigPoolStakeMap :: [(PoolStake, NonEmpty RelayAccessPoint)]
133135
-> Map AccPoolStake (PoolStake, NonEmpty RelayAccessPoint)
134136
accBigPoolStakeMap = Map.fromAscList -- the input list is ordered by `AccPoolStake`, thus we
135137
-- can use `fromAscList`
136-
. accBigPoolStake
138+
. accumulateBigLedgerStake
137139

138140
-- | Try to pick n random peers using stake distribution.
139141
--

0 commit comments

Comments
 (0)