Skip to content

Commit 7435426

Browse files
authored
simulation: first stab at full leios support (#264)
1 parent 9bc4143 commit 7435426

File tree

9 files changed

+230
-69
lines changed

9 files changed

+230
-69
lines changed

data/simulation/config.d.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,17 @@ export interface Config {
3838
/**
3939
* The expected time it takes a header to fully diffuse across the network.
4040
* This is Δhdr from the Leios paper.
41-
*
42-
* Only supported by Rust simulation. */
41+
* */
4342
"leios-header-diffusion-time-ms": number;
44-
43+
/**
44+
* Praos blockchain quality parameter.
45+
* This is η from the Leios paper.
46+
* Controls the pipelines EBs should reference in Full leios:
47+
* i - ⌈3η/L⌉, …, i-3
48+
* where i is the index of the current pipeline.
49+
*
50+
* Only supported by Haskell simulation */
51+
"praos-chain-quality": number;
4552
// Transaction Configuration
4653
/** Only supported by Rust simulation. */
4754
"tx-generation-distribution": Distribution;

data/simulation/config.default.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ leios-stage-length-slots: 20
2727
leios-stage-active-voting-slots: 1
2828
leios-vote-send-recv-stages: false
2929
leios-header-diffusion-time-ms: 1000.0
30+
# TODO: revise default
31+
praos-chain-quality: 20
3032

3133
################################################################################
3234
# Transaction Configuration

data/simulation/config.schema.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@
249249
"type": "number"
250250
},
251251
"leios-header-diffusion-time-ms": {
252-
"description": "The expected time it takes a header to fully diffuse across the network.\nThis is Δhdr from the Leios paper.\n\nOnly supported by Rust simulation.",
252+
"description": "The expected time it takes a header to fully diffuse across the network.\nThis is Δhdr from the Leios paper.",
253253
"type": "number"
254254
},
255255
"leios-stage-active-voting-slots": {
@@ -274,6 +274,10 @@
274274
"description": "Only supported by Haskell simulation.",
275275
"type": "boolean"
276276
},
277+
"praos-chain-quality": {
278+
"description": "Praos blockchain quality parameter.\nThis is η from the Leios paper.\nControls the pipelines EBs should reference in Full leios:\n i - ⌈3η/L⌉, …, i-3\nwhere i is the index of the current pipeline.\n\nOnly supported by Haskell simulation",
279+
"type": "number"
280+
},
277281
"rb-body-legacy-praos-payload-avg-size-bytes": {
278282
"additionalProperties": false,
279283
"properties": {},

simulation/src/LeiosProtocol/Common.hs

+7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module LeiosProtocol.Common (
4040
where
4141

4242
import Chan.TCP
43+
import Control.DeepSeq
4344
import Control.Exception (assert)
4445
import Control.Monad (guard)
4546
import Data.Aeson
@@ -103,6 +104,9 @@ data InputBlockId = InputBlockId
103104
deriving stock (Eq, Ord, Show, Generic)
104105
deriving anyclass (Hashable)
105106

107+
instance NFData InputBlockId where
108+
rnf InputBlockId{} = ()
109+
106110
newtype SubSlotNo = SubSlotNo Word8
107111
deriving stock (Show)
108112
deriving newtype (Eq, Ord, Num, Enum, Bounded)
@@ -150,6 +154,9 @@ data EndorseBlockId = EndorseBlockId
150154
deriving stock (Eq, Ord, Show, Generic)
151155
deriving anyclass (Hashable)
152156

157+
instance NFData EndorseBlockId where
158+
rnf EndorseBlockId{} = ()
159+
153160
data EndorseBlock = EndorseBlock
154161
{ id :: !EndorseBlockId
155162
, slot :: !SlotNo

simulation/src/LeiosProtocol/Config.hs

+22
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ instance Default CleanupPolicies where
8585
allCleanupPolicies :: CleanupPolicies
8686
allCleanupPolicies = CleanupPolicies $ Set.fromList [minBound .. maxBound]
8787

88+
data LeiosVariant = Short | Full
89+
deriving (Show, Eq, Generic)
90+
8891
data Config = Config
8992
{ relayStrategy :: RelayStrategy
9093
, tcpCongestionControl :: Bool
@@ -95,6 +98,9 @@ data Config = Config
9598
, leiosStageLengthSlots :: Word
9699
, leiosStageActiveVotingSlots :: Word
97100
, leiosVoteSendRecvStages :: Bool
101+
, leiosVariant :: LeiosVariant
102+
, leiosHeaderDiffusionTimeMs :: DurationMs
103+
, praosChainQuality :: Double
98104
, txGenerationDistribution :: Distribution
99105
, txSizeBytesDistribution :: Distribution
100106
, txValidationCpuTimeMs :: DurationMs
@@ -164,6 +170,9 @@ instance Default Config where
164170
, leiosStageLengthSlots = 20
165171
, leiosStageActiveVotingSlots = 1
166172
, leiosVoteSendRecvStages = False
173+
, leiosVariant = Short
174+
, leiosHeaderDiffusionTimeMs = 1000
175+
, praosChainQuality = 20
167176
, txGenerationDistribution = Exp{lambda = 0.85, scale = Just 1000}
168177
, txSizeBytesDistribution = LogNormal{mu = 6.833, sigma = 1.127}
169178
, txValidationCpuTimeMs = 1.5
@@ -233,6 +242,9 @@ configToKVsWith getter cfg =
233242
, get @"multiplexMiniProtocols" getter cfg
234243
, get @"treatBlocksAsFull" getter cfg
235244
, get @"cleanupPolicies" getter cfg
245+
, get @"leiosVariant" getter cfg
246+
, get @"leiosHeaderDiffusionTimeMs" getter cfg
247+
, get @"praosChainQuality" getter cfg
236248
, get @"simulateTransactions" getter cfg
237249
, get @"leiosStageLengthSlots" getter cfg
238250
, get @"leiosStageActiveVotingSlots" getter cfg
@@ -316,6 +328,9 @@ instance FromJSON Config where
316328
multiplexMiniProtocols <- parseFieldOrDefault @Config @"multiplexMiniProtocols" obj
317329
treatBlocksAsFull <- parseFieldOrDefault @Config @"treatBlocksAsFull" obj
318330
cleanupPolicies <- parseFieldOrDefault @Config @"cleanupPolicies" obj
331+
leiosVariant <- parseFieldOrDefault @Config @"leiosVariant" obj
332+
leiosHeaderDiffusionTimeMs <- parseFieldOrDefault @Config @"leiosHeaderDiffusionTimeMs" obj
333+
praosChainQuality <- parseFieldOrDefault @Config @"praosChainQuality" obj
319334
simulateTransactions <- parseFieldOrDefault @Config @"simulateTransactions" obj
320335
leiosStageLengthSlots <- parseFieldOrDefault @Config @"leiosStageLengthSlots" obj
321336
leiosStageActiveVotingSlots <- parseFieldOrDefault @Config @"leiosStageActiveVotingSlots" obj
@@ -465,6 +480,13 @@ instance ToJSON RelayStrategy where
465480
toJSON = genericToJSON defaultEnumOptions
466481
toEncoding = genericToEncoding defaultEnumOptions
467482

483+
instance FromJSON LeiosVariant where
484+
parseJSON = genericParseJSON defaultEnumOptions
485+
486+
instance ToJSON LeiosVariant where
487+
toJSON = genericToJSON defaultEnumOptions
488+
toEncoding = genericToEncoding defaultEnumOptions
489+
468490
-- | Create a 'Config' from a file.
469491
readConfigEither :: FilePath -> IO (Either ParseException Config)
470492
readConfigEither = Yaml.decodeFileEither

simulation/src/LeiosProtocol/Short.hs

+94-25
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
module LeiosProtocol.Short (module LeiosProtocol.Short, DiffusionStrategy (..)) where
1818

1919
import Chan (mkConnectionConfig)
20+
import Control.DeepSeq
2021
import Control.Exception (assert)
2122
import Control.Monad (guard)
2223
import Data.Kind
@@ -105,6 +106,11 @@ data LeiosConfig = forall p. IsPipeline p => LeiosConfig
105106
-- ^ maximum age of an uncertified endorsement block before it expires
106107
, cleanupPolicies :: CleanupPolicies
107108
-- ^ active cleanup policies
109+
, variant :: LeiosVariant
110+
, headerDiffusionTime :: NominalDiffTime
111+
-- ^ Δ_{hdr}.
112+
, pipelinesToReferenceFromEB :: Int
113+
-- ^ how many older pipelines to reference from an EB when `variant = Full`.
108114
, votingFrequencyPerStage :: Double
109115
, voteSendStage :: Stage p
110116
, votesForCertificate :: Int
@@ -125,17 +131,25 @@ convertConfig disk =
125131
else (\x -> x)
126132
)
127133
$ case voting of
128-
SomeStage pipeline voteSendStage ->
134+
SomeStage pipeline voteSendStage -> do
135+
let sliceLength = fromIntegral disk.leiosStageLengthSlots
129136
LeiosConfig
130137
{ praos
131138
, pipeline
132139
, voteSendStage
133-
, sliceLength = fromIntegral disk.leiosStageLengthSlots
140+
, sliceLength
134141
, inputBlockFrequencyPerSlot = disk.ibGenerationProbability
135142
, endorseBlockFrequencyPerStage = disk.ebGenerationProbability
136143
, maxEndorseBlockAgeSlots = fromIntegral disk.ebMaxAgeSlots
137144
, maxEndorseBlockAgeForRelaySlots = fromIntegral disk.ebMaxAgeForRelaySlots
138145
, cleanupPolicies = disk.cleanupPolicies
146+
, variant = disk.leiosVariant
147+
, headerDiffusionTime = realToFrac $ durationMsToDiffTime disk.leiosHeaderDiffusionTimeMs
148+
, pipelinesToReferenceFromEB =
149+
if disk.leiosVariant == Full
150+
then
151+
ceiling ((3 * disk.praosChainQuality) / fromIntegral sliceLength) - 2
152+
else 0
139153
, activeVotingStageLength = fromIntegral disk.leiosStageActiveVotingSlots
140154
, votingFrequencyPerStage = disk.voteGenerationProbability
141155
, votesForCertificate = fromIntegral disk.voteThreshold
@@ -210,6 +224,8 @@ convertConfig disk =
210224
fromIntegral $
211225
disk.ebSizeBytesConstant
212226
+ disk.ebSizeBytesPerIb `forEach` eb.inputBlocks
227+
-- TODO: make it a per-ref field.
228+
+ disk.ebSizeBytesPerIb `forEach` eb.endorseBlocksEarlierPipeline
213229
, voteMsg = \vt ->
214230
fromIntegral $
215231
disk.voteBundleSizeBytesConstant
@@ -265,6 +281,9 @@ delaysAndSizesAsFull cfg@LeiosConfig{pipeline, voteSendStage} =
265281
, maxEndorseBlockAgeSlots = cfg.maxEndorseBlockAgeSlots
266282
, maxEndorseBlockAgeForRelaySlots = fromIntegral cfg.maxEndorseBlockAgeForRelaySlots
267283
, cleanupPolicies = cfg.cleanupPolicies
284+
, variant = cfg.variant
285+
, headerDiffusionTime = cfg.headerDiffusionTime
286+
, pipelinesToReferenceFromEB = cfg.pipelinesToReferenceFromEB
268287
, activeVotingStageLength = cfg.activeVotingStageLength
269288
, votingFrequencyPerStage = cfg.votingFrequencyPerStage
270289
, voteSendStage = voteSendStage
@@ -395,7 +414,7 @@ instance IsPipeline a => Bounded (Stage a) where
395414
[] -> undefined
396415
maxBound = last allStages
397416

398-
inRange :: SlotNo -> (SlotNo, SlotNo) -> Bool
417+
inRange :: Ord a => a -> (a, a) -> Bool
399418
inRange s (a, b) = a <= s && s <= b
400419

401420
rangePrefix :: Int -> (SlotNo, SlotNo) -> (SlotNo, SlotNo)
@@ -473,10 +492,16 @@ proposeRange :: LeiosConfig -> PipelineNo -> (SlotNo, SlotNo)
473492
proposeRange cfg@LeiosConfig{pipeline = (_ :: SingPipeline p)} p =
474493
stageRangeOf @p cfg p Propose
475494

495+
pipelineRange :: LeiosConfig -> PipelineNo -> (SlotNo, SlotNo)
496+
pipelineRange cfg p = (fst $ proposeRange cfg p, lastVoteRecv cfg p)
497+
476498
lastUnadoptedEB :: LeiosConfig -> PipelineNo -> SlotNo
477499
lastUnadoptedEB leios@LeiosConfig{pipeline = (_ :: SingPipeline p), maxEndorseBlockAgeSlots} pipelineNo =
478500
lastVoteRecv leios pipelineNo + toEnum maxEndorseBlockAgeSlots
479501

502+
endorseBlockPipeline :: LeiosConfig -> EndorseBlock -> PipelineNo
503+
endorseBlockPipeline cfg@LeiosConfig{pipeline = _ :: SingPipeline p} eb = pipelineOf @p cfg Endorse eb.slot
504+
480505
----------------------------------------------------------------------------------------------
481506
---- Smart constructors
482507
----------------------------------------------------------------------------------------------
@@ -510,21 +535,27 @@ mkInputBlock _cfg header bodySize = assert (messageSizeBytes ib >= segmentSize)
510535
ib = InputBlock{header, body = InputBlockBody{id = header.id, size = bodySize, slot = header.slot}}
511536

512537
mkEndorseBlock ::
513-
LeiosConfig -> EndorseBlockId -> SlotNo -> NodeId -> [InputBlockId] -> EndorseBlock
514-
mkEndorseBlock cfg@LeiosConfig{pipeline = _ :: SingPipeline p} id slot producer inputBlocks =
515-
-- Endorse blocks are produced at the beginning of the stage.
516-
assert (stageStart @p cfg Endorse slot Endorse == Just slot) $
517-
fixSize cfg $
518-
EndorseBlock{endorseBlocksEarlierStage = [], endorseBlocksEarlierPipeline = [], size = 0, ..}
538+
LeiosConfig -> EndorseBlockId -> SlotNo -> NodeId -> [EndorseBlockId] -> [InputBlockId] -> EndorseBlock
539+
mkEndorseBlock cfg@LeiosConfig{pipeline = _ :: SingPipeline p} id slot producer endorseBlocksEarlierPipeline inputBlocks =
540+
assert (cfg.variant == Full || null endorseBlocksEarlierPipeline) $
541+
-- Endorse blocks are produced at the beginning of the stage.
542+
assert (stageStart @p cfg Endorse slot Endorse == Just slot) $
543+
rnf endorseBlocksEarlierPipeline `seq`
544+
rnf inputBlocks `seq`
545+
fixSize
546+
cfg
547+
EndorseBlock{endorseBlocksEarlierStage = [], size = 0, ..}
519548

520549
mockEndorseBlock :: LeiosConfig -> Int -> EndorseBlock
521550
mockEndorseBlock cfg n =
522-
mkEndorseBlock
523-
cfg
524-
(EndorseBlockId (NodeId 0) 0)
525-
0
526-
(NodeId 0)
527-
[InputBlockId (NodeId 0) i | i <- [0 .. n - 1]]
551+
assert (cfg.variant /= Full) $
552+
mkEndorseBlock
553+
cfg
554+
(EndorseBlockId (NodeId 0) 0)
555+
0
556+
(NodeId 0)
557+
[]
558+
[InputBlockId (NodeId 0) i | i <- [0 .. n - 1]]
528559

529560
mockFullEndorseBlock :: LeiosConfig -> EndorseBlock
530561
mockFullEndorseBlock cfg = mockEndorseBlock cfg $ cfg.sliceLength * (ceiling cfg.inputBlockFrequencyPerSlot)
@@ -613,8 +644,10 @@ newtype InputBlocksSnapshot = InputBlocksSnapshot
613644
{ validInputBlocks :: InputBlocksQuery -> [InputBlockId]
614645
}
615646

616-
newtype EndorseBlocksSnapshot = EndorseBlocksSnapshot
647+
data EndorseBlocksSnapshot = EndorseBlocksSnapshot
617648
{ validEndorseBlocks :: (SlotNo, SlotNo) -> [EndorseBlock]
649+
, -- , endorseBlocksInChain :: (SlotNo, SlotNo) -> [EndorseBlock]
650+
certifiedEndorseBlocks :: (PipelineNo, PipelineNo) -> [(PipelineNo, [(EndorseBlock, Certificate, UTCTime)])]
618651
}
619652

620653
-- | Both constraints are inclusive.
@@ -640,16 +673,37 @@ inputBlocksToEndorse cfg@LeiosConfig{pipeline = _ :: SingPipeline p} current buf
640673
, receivedBy
641674
}
642675

676+
-- | Returns possible EBs to reference from current pipeline EB.
677+
endorseBlocksToReference ::
678+
LeiosConfig ->
679+
PipelineNo ->
680+
EndorseBlocksSnapshot ->
681+
(PipelineNo -> UTCTime -> Bool) ->
682+
[(PipelineNo, [EndorseBlock])]
683+
endorseBlocksToReference LeiosConfig{variant = Short} _ _ _ = []
684+
endorseBlocksToReference cfg@LeiosConfig{variant = Full} pl EndorseBlocksSnapshot{..} checkDeliveryTime =
685+
[ (p, [eb | (eb, _, _) <- es])
686+
| (p, es) <- ebs
687+
, or [checkDeliveryTime p t | (_, _, t) <- es]
688+
]
689+
where
690+
newestPL = toEnum $ max 0 $ fromEnum pl - 2
691+
oldestPL = toEnum $ max 0 $ fromEnum newestPL - cfg.pipelinesToReferenceFromEB
692+
ebs = certifiedEndorseBlocks (oldestPL, newestPL)
693+
643694
shouldVoteOnEB ::
644695
LeiosConfig ->
696+
SlotConfig ->
645697
-- | current slot
646698
SlotNo ->
647699
InputBlocksSnapshot ->
700+
EndorseBlocksSnapshot ->
648701
EndorseBlock ->
649702
Bool
650-
shouldVoteOnEB cfg@LeiosConfig{voteSendStage} slot _buffers
703+
shouldVoteOnEB cfg@LeiosConfig{voteSendStage} _ slot _buffers _
704+
-- checks whether a pipeline has been started before.
651705
| Nothing <- stageRange cfg voteSendStage slot Propose = const False
652-
shouldVoteOnEB cfg@LeiosConfig{voteSendStage} slot buffers = cond
706+
shouldVoteOnEB cfg@LeiosConfig{voteSendStage} slotConfig slot buffers ebuffers = cond
653707
where
654708
generatedBetween = fromMaybe (error "impossible") $ stageRange cfg voteSendStage slot Propose
655709
receivedByEndorse =
@@ -669,35 +723,50 @@ shouldVoteOnEB cfg@LeiosConfig{voteSendStage} slot buffers = cond
669723
-- Note: maybe order on (slot, subSlot, vrf proof) should be used instead?
670724
subset xs ys = all (`elem` ys) xs
671725

726+
endOfPipelineTime p = slotTime slotConfig (snd (pipelineRange cfg p))
727+
672728
cond :: EndorseBlock -> Bool
673-
cond eb = assert assumptions $ acd && b
729+
cond eb = assert assumptions $ acd && b && full
674730
where
675731
assumptions =
676732
null eb.endorseBlocksEarlierStage
677-
&& null eb.endorseBlocksEarlierPipeline
733+
&& (null eb.endorseBlocksEarlierPipeline || cfg.variant == Full)
678734
&& eb.slot `inRange` fromMaybe (error "impossible") (stageRange cfg voteSendStage slot Endorse)
679735
-- A. all referenced IBs have been received by the end of the Endorse stage,
680736
-- C. all referenced IBs validate (wrt. script execution), and,
681737
-- D. only IBs from this pipeline’s Propose stage are referenced (and not from other pipelines).
682738
acd = eb.inputBlocks `subset` receivedByEndorse
683739
-- B. all IBs seen by the end of the Deliver 1 stage are referenced,
684740
b = receivedByDeliver1 `subset` eb.inputBlocks
741+
-- assumes eb.endorseBlocksEarlierPipeline are in pipeline order.
742+
full =
743+
and $
744+
zipWith elem eb.endorseBlocksEarlierPipeline $
745+
[ map (.id) es
746+
| (_, es) <-
747+
endorseBlocksToReference
748+
cfg
749+
(endorseBlockPipeline cfg eb)
750+
ebuffers
751+
( \p t ->
752+
addUTCTime cfg.headerDiffusionTime t < endOfPipelineTime p
753+
)
754+
, not (null es)
755+
]
685756

686757
endorseBlocksToVoteFor ::
687758
LeiosConfig ->
759+
SlotConfig ->
688760
-- | current slot
689761
SlotNo ->
690762
InputBlocksSnapshot ->
691763
EndorseBlocksSnapshot ->
692764
[EndorseBlock]
693-
endorseBlocksToVoteFor cfg@LeiosConfig{voteSendStage} slot ibs ebs =
694-
let cond = shouldVoteOnEB cfg slot ibs
765+
endorseBlocksToVoteFor cfg@LeiosConfig{voteSendStage} slotConfig slot ibs ebs =
766+
let cond = shouldVoteOnEB cfg slotConfig slot ibs ebs
695767
in filter cond $
696768
maybe [] ebs.validEndorseBlocks (stageRange cfg voteSendStage slot Endorse)
697769

698-
endorseBlockPipeline :: LeiosConfig -> EndorseBlock -> PipelineNo
699-
endorseBlockPipeline cfg@LeiosConfig{pipeline = _ :: SingPipeline p} eb = pipelineOf @p cfg Endorse eb.slot
700-
701770
-----------------------------------------------------------------
702771
---- Expected generation rates in each slot.
703772
-----------------------------------------------------------------

0 commit comments

Comments
 (0)