Skip to content

Commit 294b13b

Browse files
committed
add notes on Praos slot leader election for historic reasons
1 parent 60ff5f5 commit 294b13b

File tree

1 file changed

+61
-0
lines changed
  • libs/cardano-protocol-tpraos/src/Cardano/Protocol/TPraos

1 file changed

+61
-0
lines changed

libs/cardano-protocol-tpraos/src/Cardano/Protocol/TPraos/BHeader.hs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,67 @@ assertBoundedNatural maxVal val =
322322
then UnsafeBoundedNatural maxVal val
323323
else error $ show val <> " is greater than max value " <> show maxVal
324324

325+
{- Note [Background on leadership checks]
326+
In Praos, we want the target of 1 block every 20 seconds. This is why we have in the Shelley genesis file
327+
328+
"activeSlotsCoeff": 0.05 = 1 / 20 [blocks / sec]
329+
330+
To let this desired rate of block production emerge, each node runs a local lottery at each slot where 1 lovelace = 1 ticket.
331+
Note that each lovelace is fungible and we only know how much stake a pool has. Questions like which lovelace won that slot thus
332+
make no sense. This is why we emulate this lottery based on a repeated Bernoulli trial.
333+
334+
An important consequence of this emulation is that multiple pools can win the same slot (since each pool independently checks
335+
if they won). However, on average we still achieve the target rate of 1 block every 20 seconds across the network.
336+
337+
A Bernoulli trial has two outcomes (win with probability `x`, or lose with probability `1-x`).
338+
Then the odds that a pool with n tickets wins is given by
339+
340+
P("Pool with n tickets wins") = 1 - P("None of the n tickets win")
341+
= 1 - P("one ticket does not win")^n
342+
= 1 - (1-x)^n
343+
344+
Now we should remember that we target the block rate of `f = 0.05 [blocks/sec]`. So in the hypothetical case where one pool holds all
345+
stake, we should get that
346+
347+
P("Pool with all stake wins") = 1 - P("Pool has no tickets that win")
348+
= 1 - (1-x)^n
349+
= 1 - (1-x)^totalActiveStake
350+
= f
351+
352+
If we solve for x we get that the probability that 1 ticket wins is given by
353+
354+
x = 1 - (1 - f)^(1/totalActiveStake)
355+
356+
Now back to the case where stake is distributed, we can use this ideal definition of `x` via
357+
358+
P("Pool with n tickets wins") = 1 - (1- x)^n
359+
= 1 - (1 - (1 - (1 - f)^(1/totalActiveStake)) )^n
360+
= 1 - (1 - f)^(n/totalActiveStake)
361+
362+
and if we define σ = (n / totalActiveStake), then we get
363+
364+
P("Pool with stake ratio σ wins") = 1 - (1 - f)^σ
365+
366+
So how do we use this formula to locally let a pool emulate that it has a winning ticket
367+
and be able to verify that computation when validating the block corresponding to that slot?
368+
369+
This is done using a VRF, which is a keyed hash function. It takes as input a message/preimage and
370+
a VRF secret key of the pool. It outputs a hash/digest and a proof. The hash/digest has similar properties
371+
to a normal hash like collision resistance, but also that it's uniformly distributed! If the message/preimage
372+
changes, the output digest also completely changes. Very important, this VRF also allows anyone to verify
373+
that the VRF public key of the pool created that digest (using the proof). This method binds the producer to
374+
the outcome, and thus this digest can be viewed as a random draw of a uniformly distributed random variable.
375+
376+
The last trick is noting that we can compare this uniformly distributed picked value (which is a value between
377+
`0` and `2^{the digest size in bits}`) to the above Bernoulli trial probability. If it is below that value, a
378+
pool had the odds in its favor as the uniformly distributed value was below the repeated Bernoulli trial odds.
379+
380+
So if we map the VRF digest `out` to a value `0 <= p <= 1` by normalizing it, we get that we should check
381+
382+
p < 1 - (1 - f)^σ and verifyVRFOutput(Pubkey, out, proof)
383+
384+
-}
385+
325386
-- | Check that the certified VRF output, when used as a natural, is valid for
326387
-- being slot leader.
327388
checkLeaderValue ::

0 commit comments

Comments
 (0)