Note
Go to the end to download the full example code or to run this example in your browser via JupyterLite.
Proof of stake: a stake-weighted proposer lottery (Peercoin 2012)#
Weighting proposer selection by stake changes the resource used to allocate influence. This sampler isolates that one mechanism; it has no voting, slashing, randomness beacon, or finality and is not a full PoS protocol.
Compare observed selection frequencies with stake proportions. More trials tend to bring them closer; splitting one stake among names does not increase its combined expected share.
from collections import Counter
import matplotlib.pyplot as plt
import blockchainkit as bk
stakes = {"alice": 10, "bob": 30, "carol": 60}
rounds = 10_000
proposers = bk.consensus.StakeSampler(stakes, seed=42).sample(rounds)
assert proposers == bk.consensus.StakeSampler(stakes, seed=42).sample(rounds)
counts = Counter(proposers)
expected = [stake / sum(stakes.values()) for stake in stakes.values()]
observed = [counts[name] / rounds for name in stakes]
print("Observed selection fractions:", dict(zip(stakes, observed, strict=True)))
Observed selection fractions: {'alice': 0.0938, 'bob': 0.2949, 'carol': 0.6113}
Stake concentration is visible even when the lottery is fair#
fig, ax = plt.subplots(figsize=(7, 4))
xs = list(range(len(stakes)))
ax.bar([x - 0.18 for x in xs], expected, 0.36, label="Stake fraction")
ax.bar([x + 0.18 for x in xs], observed, 0.36, label="Observed proposer fraction")
ax.set(
xticks=xs,
xticklabels=list(stakes),
ylabel="Fraction",
title="Seeded proposer sampling over 10,000 rounds",
)
ax.legend()
fig.tight_layout()
plt.show()

Exercise#
Split Carol’s stake across ten names. Does her total expected influence change? Why is choosing a proposer insufficient to decide between two conflicting blocks that proposer might sign? Why is a public seed unsuitable as the sole randomness mechanism for a live consensus protocol?
A worked solution is in Exercises: consensus.
Total running time of the script: (0 minutes 0.059 seconds)