@@ -143,11 +143,11 @@ the actual payment for 2, 3 , or 4 Epochs. This is what is discussed in chapter
143143
144144
1451453. Structure expensive computation into many pure steps that can be started and stopped when the node is otherwise idle.
146- we call this pulsing, and it two must have many invariants.
146+ we call this pulsing, and it too must have many invariants.
147147 - It must be a pure computation
148148 - The computation must be serializeable, because if a node fails, we must be able to restart this
149149 computation from the last checkpoint.
150- - it must compute the same result as if we ran it all at once, not braking it into many starts and stops.
150+ - It must compute the same result as if we ran it all at once, not breaking it into many starts and stops.
151151
152152We look closely at each of these strategies in turn, studying the data structures and algorithms that implement them.
153153
@@ -381,7 +381,7 @@ of things at the beginning of the calculation, that cannot change, as the rest o
381381It also means the Reward calculation must be pipelined, and that requires a queue of SnapShots (Mark, Set, Go).
382382
383383
384- ## Break the calculation into many pure steps that can be started and stopped.
384+ ## Tools for breaking the calculation into many pure steps that can be started and stopped.
385385
386386The module `Data.Pulse` provovides a library for breaking a large computation to
387387series of smaller ones, which can be scheduled by the library.
@@ -406,5 +406,110 @@ class Pulsable (pulse :: (Type -> Type) -> Type -> Type) where
406406 then pure (current p)
407407 else do p' <- pulseM p; completeM p
408408```
409+
410+ In order to make a `Pulesable` instance we need to identify
411+ the monad `m` and the answer type `ans`. The monad type is the
412+ underlying Monad in the STS rules which is `ShelleyBase`, and the answer type
413+ is `RewardAns` which has two maps as components
409414
410415
416+ ```
417+ -- | The result of reward calculation is a pair of aggregate Maps.
418+ -- One for the accumulated answer, and one for the answer since the last pulse
419+ type RewardEvent = Map (Credential 'Staking) (Set Reward)
420+
421+ -- | The result of reward calculation is a pair of aggregate Maps.
422+ -- One for the accumulated answer, and one for the answer since the last pulse
423+ data RewardAns = RewardAns
424+ { accumRewardAns :: !(Map (Credential 'Staking) Reward)
425+ , recentRewardAns :: !RewardEvent
426+ }
427+ ```
428+
429+ ## The Pulsable Instance
430+
431+ The next step is to make an instance where we define the methods `done`, `current`, `pulseM` and `completeM`
432+
433+ ```
434+ -- | The type of a Pulser which uses 'rewardStakePoolMember' as its underlying function.
435+ -- 'rewardStakePool' will be partially applied to the component of type
436+ -- (FreeVars c) when pulsing. Note that we use two type equality (~ ) constraints
437+ -- to fix both the monad 'm' and the 'ans' type, to the context where we will use
438+ -- the type as a Pulser. The type must have 'm' and 'ans' as its last two
439+ -- parameters so we can make a Pulsable instance.
440+ -- RSLP = Reward Serializable Listbased Pulser
441+ data RewardPulser (m :: Type -> Type) ans where
442+ RSLP ::
443+ (ans ~ RewardAns, m ~ ShelleyBase) =>
444+ !Int ->
445+ !FreeVars ->
446+ !(VMap.VMap VMap.VB VMap.VP (Credential 'Staking) (CompactForm Coin)) ->
447+ !ans ->
448+ RewardPulser m ans
449+
450+ -- Because of the constraints on the Constructor RSLP, there is really only one inhabited
451+ -- type: (RewardPulser c ShelleyBase (RewardAns c))
452+ -- All of the instances are at that type. Though only the CBOR instances need make that explicit.
453+
454+ clearRecent :: RewardAns -> RewardAns
455+ clearRecent (RewardAns accum _ ) = RewardAns accum Map.empty
456+
457+ instance Pulsable RewardPulser where
458+ done (RSLP _ n _ free zs _ ans) = VMap.null zs
459+ current (RSLP _ _ _ ans) = ans
460+ pulseM p@(RSLP n free balance (clearRecent -> ans)) =
461+ if VMap.null balance
462+ then pure p
463+ else do
464+ let !(steps, !balance') = VMap.splitAt n balance
465+ ans' = VMap.foldlWithKey (rewardStakePoolMember free) ans steps
466+ pure $! RSLP n free balance' ans'
467+ completeM (RSLP _ free balance (clearRecent -> ans)) =
468+ pure $ VMap.foldlWithKey (rewardStakePoolMember free) ans balance
469+ ```
470+
471+ ## The Phases of a pusling computation
472+
473+ To prevent a huge pause in the Reward Calculation we spread out some of the computation
474+ over many blocks in the idle time of a node. We do this in 3 steps
475+
476+ 1. The Start Step of the Reward Calculation is a pure computation, computing and combining some parameters which
477+ become fixed at the time when we reach the stability point. One of these parameters is a Pulser,
478+ i.e. a computation that when pulseM'ed computes a portion of what is required, so that the whole
479+ compuation can be spread out in time. Note that this can be very cheap, as we just collecting the data
480+ that must be held constant, and creating the thunks that will execute when it is their time.
481+
482+ 2. The Pulse Step. Run the pulser for a bit. It returns a PulsingRewardUpdate, which is one of two things
483+ - The final result, if the pulser has completed
484+ - Enough information to restart the pulser for the nex pulse
485+ ```
486+ data PulsingRewUpdate
487+ = Pulsing !RewardSnapShot !Pulser -- Pulsing work still to do
488+ | Complete !RewardUpdate -- Pulsing work completed, ultimate goal reached
489+ ```
490+
491+ 3. The Complete Step. Clean up loose ends, and the return the `RewardUpdate`
492+ which can be used to update the `NewEpochState`
493+
494+ The 3 steps are combined in this function
495+
496+ ```
497+ -- | To create a reward update, run all 3 phases
498+ -- This function is not used in the rules, so it ignores RewardEvents
499+ createRUpd ::
500+ forall era.
501+ (EraGov era, EraCertState era) =>
502+ EpochSize ->
503+ BlocksMade ->
504+ EpochState era ->
505+ Coin ->
506+ ActiveSlotCoeff ->
507+ NonZero Word64 ->
508+ ShelleyBase RewardUpdate
509+ createRUpd slotsPerEpoch blocksmade epstate maxSupply asc secparam = do
510+ let step1 = startStep slotsPerEpoch blocksmade epstate maxSupply asc secparam
511+ (step2, _ event) <- pulseStep step1
512+ case step2 of
513+ Complete r -> pure r
514+ Pulsing rewsnap pulser -> fst <$> completeRupd (Pulsing rewsnap pulser)
515+ ```
0 commit comments