FizzBee
GitHub Discord Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Leader Election (Simplified Raft)

Distributed leader election with term-based voting

Demonstrates: roles, RPC calls, quorum, election safety

Large state space (2,485 nodes); run locally with fizz.

# Example 0062: Leader Election (Simplified Raft)
# Distributed leader election with term-based voting
# Demonstrates: roles, RPC calls, quorum, election safety

---
deadlock_detection: false
action_options:
    "Node.ElectionTimeout":
        max_actions: 2
---

NUM_NODES = 3

Mode = enum("Leader", "Follower", "Candidate")

@state(durable=["currentTerm", "votedFor"])
role Node:
    action Init:
        self.currentTerm = 0
        self.mode = Mode.Follower
        self.votedFor = None

    action ElectionTimeout:
        require self.mode != Mode.Leader

        # Become candidate and increment term (atomic update)
        self.mode, self.currentTerm, self.votedFor = \
            Mode.Candidate, self.currentTerm + 1, self.__id__

        candidate_term = self.currentTerm
        votes = 1  # Vote for self

        # Request votes from other nodes
        for node in nodes:
            if node.__id__ != self.__id__:
                vote_granted = node.RequestVote(self.currentTerm, self.__id__)
                if vote_granted:
                    votes = votes + 1

        # Become leader if received majority
        if self.mode == Mode.Candidate and self.currentTerm == candidate_term and votes > NUM_NODES / 2:
            self.mode = Mode.Leader

    func RequestVote(term, candidateId):
        # Reply false if term < currentTerm
        if term < self.currentTerm:
            return False

        # Update term if candidate has higher term (atomic update)
        if term > self.currentTerm:
            self.currentTerm, self.mode, self.votedFor = term, Mode.Follower, candidateId
            return True

        # Grant vote if haven't voted or already voted for this candidate (atomic update)
        if self.votedFor == None or self.votedFor == candidateId:
            self.mode, self.votedFor = Mode.Follower, candidateId
            return True

        return False

action Init:
    nodes = []
    for i in range(NUM_NODES):
        nodes.append(Node())

# Safety: at most one leader per term
always assertion AtMostOneLeaderPerTerm:
    for term_val in set([node.currentTerm for node in nodes]):
        leaders_in_term = [node for node in nodes if node.mode == Mode.Leader and node.currentTerm == term_val]
        if len(leaders_in_term) > 1:
            return False
    return True

Expected result (from fizz):

StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} action_options:{key:"Node.ElectionTimeout" value:{max_actions:2}} deadlock_detection:false
Nodes: 2485, queued: 0
Skipping dotfile generation. Too many Nodes: 2485
Valid Nodes: 2485 Unique states: 2160
IsLive: true
PASSED: Model checker completed successfully

Source on GitHub

Language features used

Roles, Assertions, Functions, Data Structures