@@ -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.
327388checkLeaderValue ::
0 commit comments