-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathleader.frg
More file actions
290 lines (250 loc) · 9.35 KB
/
Copy pathleader.frg
File metadata and controls
290 lines (250 loc) · 9.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#lang forge/temporal
/*
Abstract model of leader election in the Raft protocol. We won't represent
message passing or the RPC yet, but since leader election is core to the
protocol, we'll start here.
*/
option max_tracelength 10
abstract sig Role {}
one sig Follower, Candidate, Leader extends Role {}
sig Server {
var role: one Role,
var votedFor: lone Server,
var currentTerm: one Int
}
/////////////////////////////////////////////////////////////////////
/** The initial startup state for the cluster */
pred init {
all s: Server | {
s.role = Follower
no s.votedFor
s.currentTerm = 0
}
}
/////////////////////////////////////////////////////////////////////
/** Server `s` runs for election. */
pred startElection[s: Server] {
s.role = Follower -- GUARD
s.role' = Candidate -- ACTION: in candidate role now
s.votedFor' = s -- ACTION: votes for itself
s.currentTerm' = add[s.currentTerm, 1] -- ACTION: increments term
-- ACTION: issues RequestVote calls
-- ... we can't model this yet: no message passing
-- FRAME: role, currentTerm, votedFor for all other servers
all other: Server - s | {
other.votedFor' = other.votedFor
other.currentTerm' = other.currentTerm
other.role' = other.role
}
}
/** A server can vote for another server on request,
"on a first-come-first-served basis". */
pred makeVote[voter: Server, c: Server] {
no voter.votedFor -- GUARD: has not yet voted
voter.role in Follower + Candidate -- GUARD: avoid Leaders voting
c.role = Candidate -- GUARD: election is running
noLessUpToDateThan[c, voter] -- GUARD: candidate is no less updated
voter.votedFor' = c -- ACTION: vote for c
-- FRAME role, currentTerm for voter
-- FRAME: role, currentTerm, votedFor for all others
all s: Server | {
s.role' = s.role
s.currentTerm' = s.currentTerm
(s != voter) => (s.votedFor' = s.votedFor)
}
}
/** Does the first server have a log that is no less up-to-date than
the second server?
*/
pred noLessUpToDateThan[moreOrSame: Server, baseline: Server] {
-- true (for now); leaving this as a reminder
-- TODO: once we model logs, the paper describes this relation as:
-- the log with the later term is more up-to-date.
-- if the logs end with the same term, then the longer log is more up-to-date.
}
/** Server `s` is supported by a majority of the cluster. */
pred majorityVotes[s: Server] {
#{voter: Server | voter.votedFor = s} > divide[#Server, 2]
}
/** Server `s` wins the election. */
pred winElection[s: Server] {
-- GUARD: won the majority
majorityVotes[s]
-- ACTION: become leader, send heartbeat messages
s.role' = Leader
s.currentTerm' = s.currentTerm
no s.votedFor'
-- TODO: heartbeats
-- For now, we'll just advance their terms and cancel votes
-- directly as a FRAME, rather than using the network
all f: Server - s | {
f.role' = Follower
no f.votedFor'
f.currentTerm' = add[f.currentTerm, 1]
}
}
/** Nobody has won the election after some time. */
pred haltElection {
-- GUARD: there is some Candidate -- i.e., there is an election running
some s: Server | s.role = Candidate
-- GUARD: no server with the Candidate role has received a majority vote.
-- (There is no requirement that everyone has voted; indeed, that wouldn't
-- work since the network might be broken, etc.)
no s: Server | s.role = Candidate and majorityVotes[s]
-- ACTION: each Candidate (not each server, necessarily) will increment their term
-- and clear their vote.
all c: Server | {
c.role = Candidate => c.currentTerm' = add[c.currentTerm, 1]
else c.currentTerm' = c.currentTerm
no c.votedFor'
}
-- ACTION: initiating another round of RequestVote
-- ... we can't model this yet: no message passing
-- FRAME: nobody's role changes
all c: Server | c.role' = c.role
}
/** If a candidate or leader discovers that its term is out of date, it immediately reverts to follower state.
If the leader’s term (included in its RPC) is at least as large as the candidate’s current term, then the
candidate recognizes the leader as legitimate and returns to follower state.
*/
pred stepDown[s: Server] {
-- Two guard cases
{
-- GUARD: is leader, someone has a higher term (abstracted out message)
s.role in Leader
and
(some s2: Server-s | s2.currentTerm > s.currentTerm)
} or {
-- GUARD: is candidate, someone claims to be leader and has term no smaller
s.role in Candidate
and
(some s2: Server-s | s2.role = Leader and s2.currentTerm >= s.currentTerm)
}
-- ACTION: step down
s.role' = Follower
-- FRAME: all others equal; s same currentTerm and votedfor.
all x: Server | {
x.currentTerm' = x.currentTerm
x.votedFor' = x.votedFor
(x != s) => x.role' = x.role
}
}
/** Guardless no-op */
pred election_doNothing {
-- ACTION: no change
role' = role
votedFor' = votedFor
currentTerm' = currentTerm
}
/////////////////////////////////////////////////////////////////////
/** Allow arbitrary no-op ("stutter") transitions, a la TLA+. We'll either
assert fairness, or use some other means to avoid useless traces. */
pred electionSystemTrace {
init
always {
(some s: Server | startElection[s])
or
(some s, c: Server | makeVote[s, c])
or
(some s: Server | winElection[s])
or
(some s: Server | stepDown[s])
or
(haltElection)
or
(election_doNothing)
}
}
-----------------------------
-- VALIDATION
-----------------------------
-- Transition-system checks for combinations of transitions; no use of the trace pred yet.
test expect {
-- All of these transitions (except the no-op) should be mututally exclusive.
overlap_start_make: {eventually {some s1, s2, s3: Server | startElection[s1] and makeVote[s2, s3]}} is unsat
overlap_start_win: {eventually {some s1, s2: Server | startElection[s1] and winElection[s2]}} is unsat
overlap_start_halt: {eventually {some s1: Server | startElection[s1] and haltElection }} is unsat
overlap_make_win: {eventually {some s1, s2, s3: Server | makeVote[s1, s2] and winElection[s3]}} is unsat
overlap_make_halt: {eventually {some s1, s2: Server | makeVote[s1, s2] and haltElection}} is unsat
overlap_win_halt: {eventually {some s1: Server | winElection[s1] and haltElection}} is unsat
-- It should be possible to execute all the transitions. We'll encode this as specific
-- orderings, rather than as 4 different "eventually transition_k" checks.
-- Start -> Vote -> Win
sat_start_make_win: {
(some s: Server | startElection[s])
next_state (some s1, s2: Server | makeVote[s1, s2])
next_state next_state (some s: Server | winElection[s])
} is sat
-- Start -> Vote -> Halt
sat_start_make_halt: {
(some s: Server | startElection[s])
next_state (some s1, s2: Server | makeVote[s1, s2])
next_state next_state (haltElection)
} is sat
-- Start -> Halt
sat_start_halt: {
(some s: Server | startElection[s])
next_state (haltElection)
} is sat
-- Start -> Vote -> Win -> Start
sat_start_make_win_start: {
(some s: Server | startElection[s])
next_state (some s1, s2: Server | makeVote[s1, s2])
next_state next_state (some s: Server | winElection[s])
next_state next_state next_state (some s: Server | startElection[s])
} is sat
}
-- Transition-system checks that are aware of the trace predicate, but focus on interplay/ordering
-- of individual transitions.
test expect {
-- Cannot Halt, Vote, or Win until started
win_implies_started: {
electionSystemTrace implies
(some s: Server | winElection[s]) implies
once (some s: Server | startElection[s])
} is checked
halt_implies_started: {
electionSystemTrace implies
(haltElection) implies
once (some s: Server | startElection[s])
} is checked
vote_implies_started: {
electionSystemTrace implies
(some s1, s2: Server | makeVote[s1, s2]) implies
once (some s: Server | startElection[s])
} is checked
}
-- Domain-specific checks involving the trace pred
test expect {
-- No server should ever transition directly from `Leader` to `Candidate`.
no_direct_leader_to_candidate: {
electionSystemTrace implies
(all s: Server | {
always {s.role = Leader implies s.role' != Candidate}
})} is checked
-- It should be possible to witness two elections in a row.
two_elections_in_a_row: {
electionSystemTrace
eventually {
some s: Server | startElection[s]
next_state eventually (some s2: Server | startElection[s2])
}
} is sat
-- It should be possible for two different servers to win elections in the same trace.
two_different_winners_in_succession: {
electionSystemTrace
some disj s1, s2: Server | {
eventually s1.role = Leader
eventually s2.role = Leader
} } is sat
-- It should be invariant that there is only ever at most one `Leader`.
invariant_lone_leader: {
electionSystemTrace implies
always {lone role.Leader}
} is checked
}
viewTrace_winner: run {
electionSystemTrace
eventually {some s: Server | winElection[s]}
#Server > 1
}