The Election Circuit
One vote per node per term is the entire reason two leaders cannot exist at once.
What you're wiring up
When the clock fires, a follower promotes itself to candidate: bump the term, vote for yourself, ask every peer for their vote, all at once. Each node gives out exactly one vote per term, and that single rule does the heavy lifting. Any two majorities of the same cluster share at least one node, and that node cannot have voted twice, so two leaders in one term is arithmetically impossible.
Win a majority and you become leader, then immediately start heartbeating — empty AppendEntries whose only job is to keep everyone else's countdown from reaching zero. Lose, tie, or hear a higher term, and you fall back to follower and wait out a fresh random delay.
What this sheet buys is convergence. From a cold start the cluster settles on exactly one leader, and when you kill that leader the survivors elect another. Nothing is replicated yet; this is pure leadership.
# candidate 1 campaigns for term 4 RequestVote{term: 4, candidateId: 1} → 0, 2 0 replies {term: 4, voteGranted: true} 2 replies {term: 4, voteGranted: false} # already voted this term # 2 of 3 counting self = majority 1 → leader, heartbeats every 50ms
Assembly steps
hint
Fan out from separate goroutines and collect replies through a channel or a locked counter. Serial RPCs will blow every timing test.
hint
Granting a vote resets your election clock. Forgetting that is the classic bug that leaves the cluster in a permanent duel between candidates.
hint
Before promoting, check you are still a candidate in the same term you campaigned in. A victory that arrives after you stepped down must be ignored.
hint
Candidates keep their own clock running. A candidate that waits forever for votes deadlocks the cluster on an even split.
Go deeper (after it passes)
This is DDIA's leader-based replication chapter seen from the inside; the failover war stories there are what happens when election rules get approximated. Read section 5.2 of the paper next to your own trace/stage2.log — the trace tells you which rule you actually implemented, not which one you meant to.