@@ -19,7 +19,7 @@ module Test.Consensus.Genesis.Tests.Uniform (
1919import Cardano.Slotting.Slot (SlotNo (SlotNo ), WithOrigin (.. ))
2020import Control.Monad (replicateM )
2121import Control.Monad.Class.MonadTime.SI (Time , addTime )
22- import Data.List (intercalate , sort )
22+ import Data.List (intercalate , sort , uncons )
2323import qualified Data.List.NonEmpty as NE
2424import qualified Data.Map.Strict as Map
2525import Data.Maybe (fromMaybe , mapMaybe )
@@ -40,7 +40,8 @@ import Test.Consensus.PeerSimulator.Run (SchedulerConfig (..),
4040 defaultSchedulerConfig )
4141import Test.Consensus.PeerSimulator.StateView
4242import Test.Consensus.PointSchedule
43- import Test.Consensus.PointSchedule.Peers (Peers (.. ), isHonestPeerId )
43+ import Test.Consensus.PointSchedule.Peers (Peers (.. ), getPeerIds ,
44+ isHonestPeerId , peers' )
4445import Test.Consensus.PointSchedule.Shrinking
4546 (shrinkByRemovingAdversaries , shrinkPeerSchedules )
4647import Test.Consensus.PointSchedule.SinglePeer
@@ -72,7 +73,8 @@ tests =
7273 -- because this test writes the immutable chain to disk and `instance Binary TestBlock`
7374 -- chokes on long chains.
7475 adjustQuickCheckMaxSize (const 10 ) $
75- testProperty " the node is shut down and restarted after some time" prop_downtime
76+ testProperty " the node is shut down and restarted after some time" prop_downtime,
77+ testProperty " block fetch leashing attack" prop_blockFetchLeashingAttack
7678 ]
7779
7880theProperty ::
@@ -416,3 +418,44 @@ prop_downtime = forAllGenesisTest
416418 { pgpExtraHonestPeers = fromIntegral (gtExtraHonestPeers gt)
417419 , pgpDowntime = DowntimeWithSecurityParam (gtSecurityParam gt)
418420 }
421+
422+ prop_blockFetchLeashingAttack :: Property
423+ prop_blockFetchLeashingAttack =
424+ forAllGenesisTest
425+ (disableBoringTimeouts <$> genChains (pure 0 ) `enrichedWith` genBlockFetchLeashingSchedule)
426+ defaultSchedulerConfig
427+ { scEnableLoE = True ,
428+ scEnableLoP = True ,
429+ scEnableCSJ = True
430+ }
431+ shrinkPeerSchedules
432+ theProperty
433+ where
434+ genBlockFetchLeashingSchedule :: GenesisTest TestBlock () -> QC. Gen (PointSchedule TestBlock )
435+ genBlockFetchLeashingSchedule genesisTest = do
436+ PointSchedule {psSchedule, psMinEndTime} <-
437+ stToGen $
438+ uniformPoints
439+ (PointsGeneratorParams {pgpExtraHonestPeers = 1 , pgpDowntime = NoDowntime })
440+ (gtBlockTree genesisTest)
441+ peers <- QC. shuffle $ Map. elems $ honestPeers psSchedule
442+ let (honest, adversaries) = fromMaybe (error " blockFetchLeashingAttack" ) $ uncons peers
443+ adversaries' = map (filter (not . isBlockPoint . snd )) adversaries
444+ psSchedule' = peers' [honest] adversaries'
445+ -- Important to shuffle the order in which the peers start, otherwise the
446+ -- honest peer starts first and systematically becomes dynamo.
447+ psStartOrder <- shuffle $ getPeerIds psSchedule'
448+ pure $ PointSchedule {psSchedule = psSchedule', psStartOrder, psMinEndTime}
449+
450+ isBlockPoint :: SchedulePoint blk -> Bool
451+ isBlockPoint (ScheduleBlockPoint _) = True
452+ isBlockPoint _ = False
453+
454+ disableBoringTimeouts gt =
455+ gt
456+ { gtChainSyncTimeouts =
457+ (gtChainSyncTimeouts gt)
458+ { mustReplyTimeout = Nothing ,
459+ idleTimeout = Nothing
460+ }
461+ }
0 commit comments