Casper FFG: finality by two-thirds votes and slashing (Buterin and Griffith 2017)#

Casper the Friendly Finality Gadget overlays finality on a block chain. Validators vote for links between checkpoints; a checkpoint is justified when two thirds of the stake link to it from a justified checkpoint, and finalized when its direct child is justified. Two slashing rules (no two votes for one height; no vote surrounding another) guarantee that two conflicting finalized checkpoints cost at least a third of all stake. Ethereum’s proof of stake uses it.

What to look for#

Checkpoints are finalized one behind the justified frontier. A validator who equivocates or casts a surrounding vote is caught by comparing votes, with no need to know which fork is correct.

The history behind this experiment: Breakthroughs in Consensus. See Exercises: consensus for a worked solution to the exercise.

Justify and finalize#

import matplotlib.pyplot as plt

import blockchainkit as bk

stakes = {"v1": 25, "v2": 25, "v3": 25, "v4": 25}
ffg = bk.consensus.FinalityGadget(stakes)
history = []
for height in range(1, 7):
    voters = ["v1", "v2", "v3"] if height != 4 else ["v1", "v2"]  # Height 4 misses quorum.
    for v in voters:
        ffg.vote(v, source=max(ffg.justified), target=height)
    history.append((height, max(ffg.justified), max(ffg.finalized)))
print("justified:", sorted(ffg.justified), "finalized:", sorted(ffg.finalized))
assert 4 not in ffg.justified and 3 not in ffg.finalized
justified: [0, 1, 2, 3, 5, 6] finalized: [0, 1, 2, 5]

Slashable behaviour#

ffg.vote("v4", source=0, target=7, checkpoint=b"fork A")
ffg.vote("v4", source=0, target=7, checkpoint=b"fork B")  # Two votes for height 7.
ffg.vote("v1", source=1, target=8)  # Surrounds v1's earlier vote (5 -> 6).
offenses = {(o.validator, o.kind) for o in ffg.slashable}
print(offenses)
assert offenses == {("v4", "double vote"), ("v1", "surround vote")}
{('v4', 'double vote'), ('v1', 'surround vote')}
fig, ax = plt.subplots(figsize=(7, 3.5))
heights, justified, finalized = zip(*history, strict=True)
ax.step(heights, justified, where="post", label="latest justified")
ax.step(heights, finalized, where="post", label="latest finalized")
ax.set(
    xlabel="checkpoint voted for",
    ylabel="checkpoint height",
    title="Finality trails justification by one checkpoint",
)
ax.legend()
fig.tight_layout()
Finality trails justification by one checkpoint

Exercise#

Two conflicting checkpoints are each finalized by two thirds of the stake. Show that at least a third of the stake must have voted for both, and so broke a slashing rule.

Total running time of the script: (0 minutes 0.142 seconds)

Gallery generated by Sphinx-Gallery