Skip to content

Commit 2b39949

Browse files
committed
simulation: validate IBs once they are needed for ledger state.
- also do not cleanup certified EBs for Full leios, as they might be referenced later.
1 parent 68b83e9 commit 2b39949

File tree

2 files changed

+183
-108
lines changed

2 files changed

+183
-108
lines changed

simulation/src/LeiosProtocol/Short.hs

Lines changed: 68 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -127,71 +127,60 @@ data LeiosConfig = forall p. IsPipeline p => LeiosConfig
127127
data SomeStage = forall p. IsPipeline p => SomeStage (SingPipeline p) (Stage p)
128128

129129
convertConfig :: OnDisk.Config -> LeiosConfig
130-
convertConfig disk = checkAssertions
131-
$ ( if disk.treatBlocksAsFull
132-
then delaysAndSizesAsFull
133-
else (\x -> x)
134-
)
135-
$ case voting of
136-
SomeStage pipeline voteSendStage -> do
137-
let sliceLength = fromIntegral disk.leiosStageLengthSlots
138-
LeiosConfig
139-
{ praos
140-
, pipeline
141-
, voteSendStage
142-
, sliceLength
143-
, inputBlockFrequencyPerSlot = disk.ibGenerationProbability
144-
, endorseBlockFrequencyPerStage = disk.ebGenerationProbability
145-
, maxEndorseBlockAgeSlots = fromIntegral disk.ebMaxAgeSlots
146-
, maxEndorseBlockAgeForRelaySlots = fromIntegral disk.ebMaxAgeForRelaySlots
147-
, cleanupPolicies = disk.cleanupPolicies
148-
, variant = disk.leiosVariant
149-
, headerDiffusionTime = realToFrac $ durationMsToDiffTime disk.leiosHeaderDiffusionTimeMs
150-
, pipelinesToReferenceFromEB =
151-
if disk.leiosVariant == Full
152-
then
153-
ceiling ((3 * disk.praosChainQuality) / fromIntegral sliceLength) - 2
154-
else 0
155-
, activeVotingStageLength = fromIntegral disk.leiosStageActiveVotingSlots
156-
, votingFrequencyPerStage = disk.voteGenerationProbability
157-
, votesForCertificate = fromIntegral disk.voteThreshold
158-
, sizes
159-
, delays
160-
, ibDiffusion =
161-
RelayDiffusionConfig
162-
{ strategy = disk.ibDiffusionStrategy
163-
, maxWindowSize = disk.ibDiffusionMaxWindowSize
164-
, maxHeadersToRequest = disk.ibDiffusionMaxHeadersToRequest
165-
, maxBodiesToRequest = disk.ibDiffusionMaxBodiesToRequest
166-
}
167-
, ebDiffusion =
168-
RelayDiffusionConfig
169-
{ strategy = disk.ebDiffusionStrategy
170-
, maxWindowSize = disk.ebDiffusionMaxWindowSize
171-
, maxHeadersToRequest = disk.ebDiffusionMaxHeadersToRequest
172-
, maxBodiesToRequest = disk.ebDiffusionMaxBodiesToRequest
173-
}
174-
, voteDiffusion =
175-
RelayDiffusionConfig
176-
{ strategy = disk.voteDiffusionStrategy
177-
, maxWindowSize = disk.voteDiffusionMaxWindowSize
178-
, maxHeadersToRequest = disk.voteDiffusionMaxHeadersToRequest
179-
, maxBodiesToRequest = disk.voteDiffusionMaxBodiesToRequest
180-
}
181-
, relayStrategy = disk.relayStrategy
182-
}
130+
convertConfig disk =
131+
( if disk.treatBlocksAsFull
132+
then delaysAndSizesAsFull
133+
else (\x -> x)
134+
)
135+
$ case voting of
136+
SomeStage pipeline voteSendStage -> do
137+
let sliceLength = fromIntegral disk.leiosStageLengthSlots
138+
LeiosConfig
139+
{ praos
140+
, pipeline
141+
, voteSendStage
142+
, sliceLength
143+
, inputBlockFrequencyPerSlot = disk.ibGenerationProbability
144+
, endorseBlockFrequencyPerStage = disk.ebGenerationProbability
145+
, maxEndorseBlockAgeSlots = fromIntegral disk.ebMaxAgeSlots
146+
, maxEndorseBlockAgeForRelaySlots = fromIntegral disk.ebMaxAgeForRelaySlots
147+
, cleanupPolicies = disk.cleanupPolicies
148+
, variant = disk.leiosVariant
149+
, headerDiffusionTime = realToFrac $ durationMsToDiffTime disk.leiosHeaderDiffusionTimeMs
150+
, pipelinesToReferenceFromEB =
151+
if disk.leiosVariant == Full
152+
then
153+
ceiling ((3 * disk.praosChainQuality) / fromIntegral sliceLength) - 2
154+
else 0
155+
, activeVotingStageLength = fromIntegral disk.leiosStageActiveVotingSlots
156+
, votingFrequencyPerStage = disk.voteGenerationProbability
157+
, votesForCertificate = fromIntegral disk.voteThreshold
158+
, sizes
159+
, delays
160+
, ibDiffusion =
161+
RelayDiffusionConfig
162+
{ strategy = disk.ibDiffusionStrategy
163+
, maxWindowSize = disk.ibDiffusionMaxWindowSize
164+
, maxHeadersToRequest = disk.ibDiffusionMaxHeadersToRequest
165+
, maxBodiesToRequest = disk.ibDiffusionMaxBodiesToRequest
166+
}
167+
, ebDiffusion =
168+
RelayDiffusionConfig
169+
{ strategy = disk.ebDiffusionStrategy
170+
, maxWindowSize = disk.ebDiffusionMaxWindowSize
171+
, maxHeadersToRequest = disk.ebDiffusionMaxHeadersToRequest
172+
, maxBodiesToRequest = disk.ebDiffusionMaxBodiesToRequest
173+
}
174+
, voteDiffusion =
175+
RelayDiffusionConfig
176+
{ strategy = disk.voteDiffusionStrategy
177+
, maxWindowSize = disk.voteDiffusionMaxWindowSize
178+
, maxHeadersToRequest = disk.voteDiffusionMaxHeadersToRequest
179+
, maxBodiesToRequest = disk.voteDiffusionMaxBodiesToRequest
180+
}
181+
, relayStrategy = disk.relayStrategy
182+
}
183183
where
184-
checkAssertions (cfg :: LeiosConfig) =
185-
if cfg.variant /= Full || (oldestEBToReference < cfg.maxEndorseBlockAgeSlots)
186-
then cfg
187-
else error $ "Parameter `eb-max-age-slots` should be greater than " ++ show oldestEBToReference ++ " given the chosen praos-chain-quality and leios-stage-length-slots."
188-
where
189-
oldestEBToReference =
190-
( ceiling ((3 * disk.praosChainQuality) / fromIntegral cfg.sliceLength)
191-
+ 1 {-Endorse-}
192-
+ 1 {- Vote (Send) -}
193-
)
194-
* cfg.sliceLength
195184
forEach n xs = n * fromIntegral (length @[] xs)
196185
forEachKey n m = n * fromIntegral (Map.size m)
197186
durationMsToDiffTime (DurationMs d) = secondsToDiffTime $ d / 1000
@@ -508,10 +497,6 @@ proposeRange cfg@LeiosConfig{pipeline = (_ :: SingPipeline p)} p =
508497
pipelineRange :: LeiosConfig -> PipelineNo -> (SlotNo, SlotNo)
509498
pipelineRange cfg p = (fst $ proposeRange cfg p, lastVoteRecv cfg p)
510499

511-
lastUnadoptedEB :: LeiosConfig -> PipelineNo -> SlotNo
512-
lastUnadoptedEB leios@LeiosConfig{pipeline = (_ :: SingPipeline p), maxEndorseBlockAgeSlots} pipelineNo =
513-
lastVoteRecv leios pipelineNo + toEnum maxEndorseBlockAgeSlots
514-
515500
endorseBlockPipeline :: LeiosConfig -> EndorseBlock -> PipelineNo
516501
endorseBlockPipeline cfg@LeiosConfig{pipeline = _ :: SingPipeline p} eb = pipelineOf @p cfg Endorse eb.slot
517502

@@ -712,22 +697,25 @@ endorseBlocksToReference cfg@LeiosConfig{variant = Full} pl EndorseBlocksSnapsho
712697
where
713698
result =
714699
[ (p, [eb | (eb, _, _) <- es])
715-
| plRange <- maybeToList $ pipelinesToReferenceFromEB cfg.pipelinesToReferenceFromEB pl
700+
| plRange <- maybeToList $ pipelinesToReferenceFromEB cfg pl
716701
, (p, es) <- certifiedEndorseBlocks plRange
717702
, or [checkDeliveryTime p t | (_, _, t) <- es]
718703
]
719704

720-
pipelinesToReferenceFromEB :: Int -> PipelineNo -> Maybe (PipelineNo, PipelineNo)
721-
pipelinesToReferenceFromEB n pl = do
722-
predPl <- safePred pl
723-
case fromEnum predPl - maxStagesAfterEndorse of
724-
newestIx
725-
| newestIx < 0 -> Nothing
726-
| otherwise ->
727-
Just
728-
( toEnum $ max 0 $ newestIx - (n - 1)
729-
, toEnum newestIx
730-
)
705+
pipelinesToReferenceFromEB :: LeiosConfig -> PipelineNo -> Maybe (PipelineNo, PipelineNo)
706+
pipelinesToReferenceFromEB cfg pl
707+
| Full <- cfg.variant = do
708+
let n = cfg.pipelinesToReferenceFromEB
709+
predPl <- safePred pl
710+
case fromEnum predPl - maxStagesAfterEndorse of
711+
newestIx
712+
| newestIx < 0 -> Nothing
713+
| otherwise ->
714+
Just
715+
( toEnum $ max 0 $ newestIx - (n - 1)
716+
, toEnum newestIx
717+
)
718+
| otherwise = Nothing
731719
where
732720
maxStagesAfterEndorse = 2
733721
safePred x = do

simulation/src/LeiosProtocol/Short/Node.hs

Lines changed: 115 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ data LeiosNodeState m = LeiosNodeState
139139
-- ^ waiting for ledger state of RB block to be validated.
140140
, ledgerStateVar :: !(TVar m (Map (HeaderHash RankingBlock) LedgerState))
141141
, ibsNeededForEBVar :: !(TVar m (Map EndorseBlockId (Set InputBlockId)))
142+
, ibsValidationActionsVar :: !(TVar m (Map InputBlockId (STM m ())))
142143
, votesForEBVar :: !(TVar m (Map EndorseBlockId CertificateProgress))
143144
}
144145

@@ -401,6 +402,7 @@ newLeiosNodeState cfg = do
401402
waitingForLedgerStateVar <- newTVarIO Map.empty
402403
taskQueue <- atomically $ newTaskMultiQueue cfg.processingQueueBound
403404
votesForEBVar <- newTVarIO Map.empty
405+
ibsValidationActionsVar <- newTVarIO Map.empty
404406
return $ LeiosNodeState{..}
405407

406408
leiosNode ::
@@ -495,7 +497,10 @@ leiosNode tracer cfg followers peers = do
495497

496498
let blockGenerationThreads = [generator tracer cfg leiosState]
497499

498-
let computeLedgerStateThreads = [computeLedgerStateThread tracer cfg leiosState]
500+
let computeLedgerStateThreads =
501+
[ computeLedgerStateThread tracer cfg leiosState
502+
-- , validateIBsOfCertifiedEBs tracer cfg leiosState
503+
]
499504

500505
let pruningThreads =
501506
concat
@@ -507,6 +512,9 @@ leiosNode tracer cfg followers peers = do
507512
]
508513
, [ pruneExpiredUnadoptedEBs tracer cfg leiosState
509514
| CleanupExpiredUnadoptedEb `isEnabledIn` cfg.leios.cleanupPolicies
515+
, -- With Full a fresh EB might end up referencing all the way to Genesis.
516+
-- TODO: could expire EBs not referenced by young enough EBs.
517+
cfg.leios.variant /= Full
510518
]
511519
, [ pruneExpiredIBs tracer cfg leiosState
512520
| CleanupExpiredIb `isEnabledIn` cfg.leios.cleanupPolicies
@@ -543,7 +551,7 @@ pruneExpiredIBs _tracer LeiosNodeConfig{leios, slotConfig} st = go (toEnum 0)
543551
-- traceWith tracer $! LeiosNodeEvent Pruned (EventIB ib)
544552
go (succ p)
545553

546-
-- rEB slots after the end of vote-receiving,
554+
-- rEB slots after the end of Endorse,
547555
-- prune EBs that became certified but were not adopted by an RB.
548556
pruneExpiredUnadoptedEBs ::
549557
forall m.
@@ -673,44 +681,107 @@ pruneExpiredVotes _tracer LeiosNodeConfig{leios = leios@LeiosConfig{pipeline = _
673681
-- traceWith tracer $! LeiosNodeEvent Pruned (EventVote $ snd vt)
674682
go (succ p)
675683

684+
referencedEBs :: MonadSTM m => LeiosConfig -> LeiosNodeState m -> Set EndorseBlockId -> STM m [EndorseBlockId]
685+
referencedEBs cfg st ebIds0
686+
| null ebIds0 = return []
687+
| Short <- cfg.variant = pure $ Set.toList ebIds0
688+
| otherwise = do
689+
ebBuffer <- readTVar st.relayEBState.relayBufferVar
690+
let
691+
ebsReferenced :: Set EndorseBlockId -> Set EndorseBlockId -> [EndorseBlockId]
692+
ebsReferenced !fetched ebIds
693+
| null ebIds = []
694+
| otherwise = do
695+
let ebs =
696+
[ snd $ fromMaybe (error $ "EB missing:" ++ show ebId) $ RB.lookup ebBuffer ebId
697+
| ebId <- Set.toList ebIds
698+
]
699+
let fetched' = Set.union fetched ebIds
700+
let refs =
701+
Set.fromList
702+
[ refId
703+
| eb <- ebs
704+
, refId <- eb.endorseBlocksEarlierPipeline
705+
, Set.notMember refId fetched'
706+
]
707+
map (.id) ebs ++ ebsReferenced fetched' refs
708+
return $ ebsReferenced Set.empty ebIds0
709+
676710
computeLedgerStateThread ::
677711
forall m.
678712
(MonadMVar m, MonadFork m, MonadAsync m, MonadSTM m, MonadTime m, MonadDelay m) =>
679713
Tracer m LeiosNodeEvent ->
680714
LeiosNodeConfig ->
681715
LeiosNodeState m ->
682716
m ()
683-
computeLedgerStateThread tracer _cfg st = forever $ do
717+
computeLedgerStateThread tracer cfg st = forever $ do
684718
readyLedgerState <- atomically $ do
685719
-- TODO: this will get more costly as the base chain grows,
686720
-- however it grows much more slowly than anything else.
687721
chain <- PraosNode.preferredChain st.praosState
688722
let rbsOnChain = Chain.toNewestFirst $ chain
689-
let blocks = Map.fromList [(blockHash block, block) | block <- rbsOnChain]
690-
when (Map.null blocks) retry
723+
when (null rbsOnChain) retry
724+
-- TODO: should we prune the ledger state to only cover RBs on the chain?
691725
ledgerState <- readTVar st.ledgerStateVar
692-
let ledgerMissing = Map.elems $ blocks Map.\\ ledgerState
693-
when (null ledgerMissing) retry
694-
let ledgerEligible = flip filter ledgerMissing $ \blk ->
695-
case blockPrevHash blk of
696-
GenesisHash -> True
697-
BlockHash prev -> prev `Map.member` ledgerState
698-
when (null ledgerEligible) retry
699-
700-
ibsNeededForEB <- readTVar st.ibsNeededForEBVar
701-
let readyLedgerState =
702-
[ (blockHash rb, LedgerState)
703-
| rb <- ledgerMissing
704-
, flip all rb.blockBody.endorseBlocks $ \(ebId, _) ->
705-
Map.lookup ebId ibsNeededForEB == Just Set.empty
706-
]
707-
when (null readyLedgerState) retry
708-
modifyTVar' st.ledgerStateVar (`Map.union` Map.fromList readyLedgerState)
709-
return readyLedgerState
726+
let oldestMissingLedgerState = go Nothing rbsOnChain
727+
where
728+
go acc [] = acc
729+
go acc (x : xs)
730+
| Map.member (blockHash x) ledgerState = acc
731+
| otherwise = go (Just x) xs
732+
ledgerEligible <- case oldestMissingLedgerState of
733+
Nothing -> retry
734+
Just block -> pure block
735+
736+
todo <- do
737+
let doLedgerState = Left (blockHash ledgerEligible, LedgerState)
738+
case (map fst $ ledgerEligible.blockBody.endorseBlocks) of
739+
[] -> return $ doLedgerState
740+
ebIds -> do
741+
ibsNeededForEB <- readTVar st.ibsNeededForEBVar
742+
ibsNeeded <- do
743+
ebs <- referencedEBs cfg.leios st (Set.fromList ebIds)
744+
return $ Set.unions <$> mapM (flip Map.lookup ibsNeededForEB) ebs
745+
case ibsNeeded of
746+
-- Some EB was missing ibsNeeded info
747+
Nothing -> undefined
748+
Just ibs
749+
| Set.null ibs -> pure $ doLedgerState
750+
| otherwise -> pure $ Right ibs
751+
752+
case todo of
753+
Left readyLedgerState -> do
754+
modifyTVar' st.ledgerStateVar (uncurry Map.insert readyLedgerState)
755+
return [readyLedgerState]
756+
Right ibsEligibleToValidate -> do
757+
ibValActions <- readTVar st.ibsValidationActionsVar
758+
let ibsReadyToValidate = Map.elems $ Map.restrictKeys ibValActions ibsEligibleToValidate
759+
if null ibsReadyToValidate
760+
then retry
761+
else do
762+
modifyTVar' st.ibsValidationActionsVar $ flip Map.withoutKeys ibsEligibleToValidate
763+
sequence_ ibsReadyToValidate
764+
return []
710765
for_ readyLedgerState $ \(rb, _) -> do
711766
traceWith tracer $! LeiosNodeEventLedgerState rb
712767
return ()
713768

769+
-- TODO: Use or remove.
770+
-- Might be sensible to validate IBs as soon as we have a certified EB including them: the network managed to validate the IB, so a suitable ledger state is available.
771+
validateIBsOfCertifiedEBs :: MonadSTM m => Tracer m LeiosNodeEvent -> LeiosNodeConfig -> LeiosNodeState m -> m ()
772+
validateIBsOfCertifiedEBs _trace _cfg st = forever . atomically $ do
773+
ibsNeeded <- readTVar st.ibsNeededForEBVar
774+
ebs <- readTVar st.votesForEBVar
775+
let certEBs = Set.fromList [eb | (eb, Certified{}) <- Map.toList ebs]
776+
let ibsEligible = Set.unions $ Map.elems $ Map.restrictKeys ibsNeeded certEBs
777+
when (null ibsEligible) retry
778+
ibsValActions <- readTVar st.ibsValidationActionsVar
779+
let ibsToValidate = Map.toList $ Map.restrictKeys ibsValActions ibsEligible
780+
when (null ibsToValidate) $ retry
781+
forM_ ibsToValidate $ \(ibId, m) -> do
782+
modifyTVar' st.ibsValidationActionsVar $ Map.delete ibId
783+
m
784+
714785
adoptIB :: MonadSTM m => LeiosNodeState m -> InputBlock -> UTCTime -> STM m ()
715786
adoptIB leiosState ib deliveryTime = do
716787
let !ibSlot = ib.header.slot
@@ -787,12 +858,28 @@ dispatchValidation tracer cfg leiosState req =
787858
return []
788859
ValidateIBS ibs deliveryTime completion -> do
789860
-- NOTE: IBs with an RB reference have to wait for ledger state of that RB.
790-
let waitingLedgerState =
791-
[ (rbHash, [queue [valIB ib deliveryTime completion]])
792-
| ib <- ibs
793-
, BlockHash rbHash <- [(fst ib).rankingBlock]
794-
]
861+
-- However, if they get referenced by the chain they should be validated anyway.
862+
-- We use a map to store the validation logic, so we can force it happening in the latter case.
863+
let lookupValAction ibId = do
864+
ibValActions <- readTVar leiosState.ibsValidationActionsVar
865+
case Map.lookup ibId ibValActions of
866+
Just m -> do
867+
modifyTVar' leiosState.ibsValidationActionsVar $
868+
Map.delete ibId
869+
m
870+
Nothing -> pure ()
871+
let storeAction rbHash ib@(h, _) = do
872+
modifyTVar' leiosState.ibsValidationActionsVar $
873+
Map.insert h.id (queue [valIB ib deliveryTime completion])
874+
return (rbHash, [lookupValAction $ (fst ib).id])
875+
waitingLedgerState <-
876+
sequence $
877+
[ storeAction rbHash ib
878+
| ib <- ibs
879+
, BlockHash rbHash <- [(fst ib).rankingBlock]
880+
]
795881

882+
-- TODO: cancel the ones forced by computeLedgerState
796883
waitFor
797884
leiosState.waitingForLedgerStateVar
798885
waitingLedgerState

0 commit comments

Comments
 (0)