├── .gitignore ├── DiskPaxos.tla ├── DistributedMultiPaxos.tla ├── LICENSE.md ├── MCDiskPaxos.tla ├── MultiConsensus.tla ├── MultiPaxos.pdf ├── MultiPaxos.tla └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## By default, ignore everything 2 | * 3 | ## Do not ignore .gitignore 4 | !/.gitignore 5 | ## Do not ignore the spec files 6 | !/*.tla 7 | ## Do not ignore the relevant model files/folders 8 | /*.toolbox/* 9 | !/*.toolbox/ 10 | !/*.toolbox/.project 11 | !/*.toolbox/*.launch 12 | ## Ignore everything in .settings/ except the pref file 13 | /*.toolbox/.settings/* 14 | !/*.toolbox/.settings/ 15 | !/*.toolbox/.settings/org.lamport.tla.toolbox.prefs 16 | 17 | -------------------------------------------------------------------------------- /DiskPaxos.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE DiskPaxos ----------------------------- 2 | 3 | (***************************************************************************) 4 | (* A formalization of the SWMR-Shared-Memory Disk Paxos, as described in *) 5 | (* Lamport and Gafni's paper. *) 6 | (* *) 7 | (* The idea of the algorithm is that each process has a SWMR register. A *) 8 | (* process starts round r by setting the mbal component of its register to *) 9 | (* r. Then it reads all the registers. If none of the have a round *) 10 | (* greater than r, the process computes a safe value, and then writes this *) 11 | (* value in the inp component of its register and writes r to the bal *) 12 | (* component. Finally it reads again all the registers and decides if *) 13 | (* none have a greater ballot than r. *) 14 | (* *) 15 | (* This is an interesting view on Paxos. Can we base the correctness *) 16 | (* proof on the same notions as for classic Paxos? I.e. what about ballot *) 17 | (* arrays? What about the Choosable predicate and its monotonicity? *) 18 | (* *) 19 | (* A value is choosable at ballot b if the owner p of b can complete its *) 20 | (* ballot with v. See below for a definition. *) 21 | (* *) 22 | (* We have that every decided value is choosable. Moreover, every value *) 23 | (* making it to rs.val (in phase 2) is equal to any choosable value at a *) 24 | (* lower ballot. Proved by induction like in Paxos. *) 25 | (* *) 26 | (***************************************************************************) 27 | 28 | EXTENDS Naturals 29 | 30 | CONSTANT P, Ballots, NotABallot, V, NotAnInput 31 | \* V contains the values to decide upon. 32 | 33 | ASSUME NotABallot \notin Ballots 34 | ASSUME NotAnInput \notin V 35 | 36 | CONSTANT Bals(_) \* maps a process to its set of ballots. 37 | 38 | ASSUME \A p \in P : Bals(p) \subseteq Ballots 39 | 40 | Dblock == [mbal : Ballots \cup {NotABallot}, bal : Ballots \cup {NotABallot}, 41 | inp : V \union {NotAnInput}] 42 | 43 | Min(xs) == CHOOSE x \in xs : \A y \in xs : y >= x 44 | 45 | (* 46 | --algorithm DiskPaxos { 47 | variables 48 | \* The SWMR registers. 49 | \* Below the f[p].inp component does not matter, but it reduces the state-space to set it. 50 | rs \in {f \in [P -> Dblock] : \A p \in P : 51 | f[p].mbal = NotABallot /\ f[p].bal = NotABallot /\ f[p].inp = NotAnInput}; 52 | \* dblock[p] is a local variable. 53 | \* Here we could take anything in Bal(p) for the f[p].mbal component; again, we set it to reduce the state-space. 54 | dblock \in {f \in [P -> Dblock] : \A p \in P : 55 | f[p].mbal = Min(Bals(p)) /\ f[p].inp # NotAnInput /\ f[p].bal = NotABallot}; 56 | define { 57 | MaxBal(bals) == IF bals = {NotABallot} 58 | THEN NotABallot 59 | ELSE CHOOSE b \in bals \ {NotABallot} : \A c \in bals \ {NotABallot} : c <= b 60 | Inp(blocksRead) == 61 | LET nonInitBlks == {db \in blocksRead : db.mbal # NotABallot} 62 | \* Note that using mbal in nonInitBlks is crucial. 63 | bals == {db.bal : db \in nonInitBlks} 64 | IN 65 | IF nonInitBlks = {} THEN NotAnInput 66 | ELSE CHOOSE inp \in V : \E db \in nonInitBlks : 67 | db.inp = inp /\ db.bal = MaxBal(bals) 68 | } 69 | process (proc \in P) 70 | variables 71 | \* Does not work... so we have to use global variables. 72 | \* dblock \in {db \in Dblock : db.mbal \in Bals(self) /\ db.inp # NotAnInput}; 73 | toRead; phase = 1; 74 | blocksRead = {}; 75 | { l1: rs[self] := dblock[self]; 76 | toRead := P; 77 | l2: while (toRead # {}) 78 | with (p \in toRead, dbp = rs[p]) { 79 | if (dbp.mbal # NotABallot /\ dbp.mbal > dblock[self].mbal) { 80 | if (\E b \in Bals(self) : b > dblock[self].mbal) { 81 | dblock[self].mbal := CHOOSE b \in Bals(self) : 82 | b > dblock[self].mbal; 83 | phase := 1; 84 | goto "l1" 85 | } else { 86 | await FALSE; \* We block the process, as it ran out of ballots. 87 | } 88 | } 89 | else { 90 | blocksRead := blocksRead \union {rs[p]}; 91 | toRead := toRead \ {p} 92 | } 93 | }; 94 | l3: if (phase = 1) { 95 | dblock[self].bal := dblock[self].mbal || dblock[self].inp := 96 | IF Inp(blocksRead) = NotAnInput THEN dblock[self].inp 97 | ELSE Inp(blocksRead); 98 | phase := 2; 99 | goto "l1" 100 | } 101 | } 102 | } 103 | *) 104 | \* BEGIN TRANSLATION 105 | CONSTANT defaultInitValue 106 | VARIABLES rs, dblock, pc 107 | 108 | (* define statement *) 109 | MaxBal(bals) == IF bals = {NotABallot} 110 | THEN NotABallot 111 | ELSE CHOOSE b \in bals \ {NotABallot} : \A c \in bals \ {NotABallot} : c <= b 112 | Inp(blocksRead) == 113 | LET nonInitBlks == {db \in blocksRead : db.mbal # NotABallot} 114 | 115 | bals == {db.bal : db \in nonInitBlks} 116 | IN 117 | IF nonInitBlks = {} THEN NotAnInput 118 | ELSE CHOOSE inp \in V : \E db \in nonInitBlks : 119 | db.inp = inp /\ db.bal = MaxBal(bals) 120 | 121 | VARIABLES toRead, phase, blocksRead 122 | 123 | vars == << rs, dblock, pc, toRead, phase, blocksRead >> 124 | 125 | ProcSet == (P) 126 | 127 | Init == (* Global variables *) 128 | /\ rs \in {f \in [P -> Dblock] : \A p \in P : 129 | f[p].mbal = NotABallot /\ f[p].bal = NotABallot /\ f[p].inp = NotAnInput} 130 | /\ dblock \in {f \in [P -> Dblock] : \A p \in P : 131 | f[p].mbal = Min(Bals(p)) /\ f[p].inp # NotAnInput /\ f[p].bal = NotABallot} 132 | (* Process proc *) 133 | /\ toRead = [self \in P |-> defaultInitValue] 134 | /\ phase = [self \in P |-> 1] 135 | /\ blocksRead = [self \in P |-> {}] 136 | /\ pc = [self \in ProcSet |-> "l1"] 137 | 138 | l1(self) == /\ pc[self] = "l1" 139 | /\ rs' = [rs EXCEPT ![self] = dblock[self]] 140 | /\ toRead' = [toRead EXCEPT ![self] = P] 141 | /\ pc' = [pc EXCEPT ![self] = "l2"] 142 | /\ UNCHANGED << dblock, phase, blocksRead >> 143 | 144 | l2(self) == /\ pc[self] = "l2" 145 | /\ IF toRead[self] # {} 146 | THEN /\ \E p \in toRead[self]: 147 | LET dbp == rs[p] IN 148 | IF dbp.mbal # NotABallot /\ dbp.mbal > dblock[self].mbal 149 | THEN /\ IF \E b \in Bals(self) : b > dblock[self].mbal 150 | THEN /\ dblock' = [dblock EXCEPT ![self].mbal = CHOOSE b \in Bals(self) : 151 | b > dblock[self].mbal] 152 | /\ phase' = [phase EXCEPT ![self] = 1] 153 | /\ pc' = [pc EXCEPT ![self] = "l1"] 154 | ELSE /\ FALSE 155 | /\ pc' = [pc EXCEPT ![self] = "l2"] 156 | /\ UNCHANGED << dblock, phase >> 157 | /\ UNCHANGED << toRead, blocksRead >> 158 | ELSE /\ blocksRead' = [blocksRead EXCEPT ![self] = blocksRead[self] \union {rs[p]}] 159 | /\ toRead' = [toRead EXCEPT ![self] = toRead[self] \ {p}] 160 | /\ pc' = [pc EXCEPT ![self] = "l2"] 161 | /\ UNCHANGED << dblock, phase >> 162 | ELSE /\ pc' = [pc EXCEPT ![self] = "l3"] 163 | /\ UNCHANGED << dblock, toRead, phase, blocksRead >> 164 | /\ rs' = rs 165 | 166 | l3(self) == /\ pc[self] = "l3" 167 | /\ IF phase[self] = 1 168 | THEN /\ dblock' = [dblock EXCEPT ![self].bal = dblock[self].mbal, 169 | ![self].inp = IF Inp(blocksRead[self]) = NotAnInput THEN dblock[self].inp 170 | ELSE Inp(blocksRead[self])] 171 | /\ phase' = [phase EXCEPT ![self] = 2] 172 | /\ pc' = [pc EXCEPT ![self] = "l1"] 173 | ELSE /\ pc' = [pc EXCEPT ![self] = "Done"] 174 | /\ UNCHANGED << dblock, phase >> 175 | /\ UNCHANGED << rs, toRead, blocksRead >> 176 | 177 | proc(self) == l1(self) \/ l2(self) \/ l3(self) 178 | 179 | Next == (\E self \in P: proc(self)) 180 | \/ (* Disjunct to prevent deadlock on termination *) 181 | ((\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars) 182 | 183 | Spec == Init /\ [][Next]_vars 184 | 185 | Termination == <>(\A self \in ProcSet: pc[self] = "Done") 186 | 187 | \* END TRANSLATION 188 | 189 | Inv1 == \A p \in P : dblock[p].bal # NotABallot => dblock[p].bal <= dblock[p].mbal 190 | 191 | Inv2 == \A p \in P : rs[p].mbal = rs[p].bal /\ rs[p].mbal # NotABallot => phase[p] = 2 192 | 193 | Agreement == \A p,q \in P : pc[p] = "Done" /\ pc[q] = "Done" => dblock[p].inp = dblock[q].inp 194 | 195 | Owner(b) == CHOOSE p \in P : b \in Bals(p) 196 | 197 | Choosable(v, b) == LET p == Owner(b) IN 198 | CASE 199 | rs[p].mbal = NotABallot \/ rs[p].mbal < b \/ (rs[p].mbal = b /\ phase[p] = 1) 200 | -> \A q \in P : rs[q].mbal = NotABallot \/ rs[q].mbal < dblock[q].mbal 201 | [] rs[p].mbal = b /\ rs[p].mbal = rs[p].bal 202 | -> rs[p].inp = v /\ (\A q \in toRead[p] : dblock[q].mbal > b => q \notin toRead[p]) 203 | [] TRUE -> FALSE 204 | 205 | Inv3 == \A v \in V : \A p \in P : 206 | (phase[p] = 2 /\ dblock[p].inp = v) => (\A b2 \in Ballots : 207 | b2 < dblock[p].bal => (\A w \in V : Choosable(w,b2) => v = w)) 208 | 209 | Inv4 == \A p \in P : pc[p] = "Done" => Choosable(rs[p].inp, rs[p].bal) 210 | 211 | ============================================================================= 212 | \* Modification History 213 | \* Last modified Fri Jan 13 18:58:38 PST 2017 by nano 214 | \* Created Mon Jan 09 08:47:33 PST 2017 by nano 215 | -------------------------------------------------------------------------------- /DistributedMultiPaxos.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE DistributedMultiPaxos ----------------------- 2 | 3 | (***************************************************************************) 4 | (* A specification of MultiPaxos that includes a model of the network. *) 5 | (* Compared to the abstract specification, processes now communicate *) 6 | (* through the network instead of directly reading each other's state. *) 7 | (* The main difference is that network messages reflect a past state of *) 8 | (* their sender, not its current state. Note that since the state of the *) 9 | (* processes is monothonic (i.e. values written in the vote array are *) 10 | (* never overwritten and ballots on increase), knowing the past state *) 11 | (* gives some information about the current state. *) 12 | (***************************************************************************) 13 | 14 | EXTENDS MultiConsensus 15 | 16 | VARIABLES 17 | ballot, vote, network, propCmds 18 | 19 | (***************************************************************************) 20 | (* We do not model learners, so no need for 2b messages *) 21 | (***************************************************************************) 22 | Msgs == 23 | {<<"1a", b>> : b \in Ballots} \cup 24 | {<<"1b", a, i, b, <>>> : i \in Instances, a \in Acceptors, 25 | b \in Ballots, maxB \in Ballots \cup {-1}, v \in V \cup {None}} \cup 26 | {<<"2a", i, b, v>> : i \in Instances, b \in Ballots, v \in V} 27 | 28 | Init == 29 | /\ ballot = [a \in Acceptors |-> -1] 30 | /\ vote = [a \in Acceptors |-> 31 | [i \in Instances |-> 32 | [b \in Ballots |-> None]]] 33 | /\ network = {} 34 | /\ propCmds = {} 35 | (* /\ 1bInfo = [b \in Ballots |-> [i \in Instances |-> None]]*) 36 | 37 | TypeInv == 38 | /\ ballot \in [Acceptors -> {-1} \cup Ballots] 39 | /\ vote \in [Acceptors -> 40 | [Instances -> 41 | [Ballots -> {None} \cup V]]] 42 | /\ network \subseteq Msgs 43 | /\ propCmds \subseteq V 44 | (* /\ 1bInfo = [Ballots -> [Instances -> V \cup {None}]]*) 45 | 46 | Propose(c) == 47 | /\ propCmds' = propCmds \cup {c} 48 | /\ UNCHANGED <> 49 | 50 | Phase1a(b) == 51 | /\ network' = network \cup {<<"1a", b>>} 52 | /\ UNCHANGED <> 53 | 54 | (***************************************************************************) 55 | (* A pair consisting of the highest ballot in which the acceptro a has *) 56 | (* voted in instance i. If a has not voted in instance i, then <<-1, *) 57 | (* None>>. *) 58 | (***************************************************************************) 59 | MaxAcceptorVote(a, i) == 60 | LET maxBallot == Max({b \in Ballots : vote[a][i][b] # None} \cup {-1}, <=) 61 | v == IF maxBallot > -1 THEN vote[a][i][maxBallot] ELSE None 62 | IN <> 63 | 64 | (***************************************************************************) 65 | (* Acceptor a receives responds from a 1a message by sending, for each *) 66 | (* instance i, its max vote in this instance. *) 67 | (***************************************************************************) 68 | Phase1b(a, b, v) == 69 | /\ ballot[a] < b 70 | /\ <<"1a", b>> \in network 71 | /\ ballot' = [ballot EXCEPT ![a] = b] 72 | /\ network' = network \cup 73 | {<<"1b", a, i, b, MaxAcceptorVote(a,i)>> : i \in Instances} 74 | /\ UNCHANGED <> 75 | 76 | 1bMsgs(b, i, Q) == 77 | {m \in network : m[1] = "1b" /\ m[2] \in Q /\ m[3] = i /\ m[4] = b} 78 | 79 | (***************************************************************************) 80 | (* The vote cast in the highest ballot less than b in instance i. This *) 81 | (* vote is unique because all ballots are conservative. Note that this *) 82 | (* can be None. *) 83 | (***************************************************************************) 84 | MaxVote(b, i, Q) == 85 | LET maxBal == Max({m[5][1] : m \in 1bMsgs(b, i, Q)}, <=) 86 | IN CHOOSE v \in V \cup {None} : \E m \in 1bMsgs(b, i, Q) : 87 | /\ m[5][1] = maxBal /\ m[5][2] = v 88 | 89 | (***************************************************************************) 90 | (* The leader of ballot b sends 2a messages when it is able to determine a *) 91 | (* safe value (i.e. when it receives 1b messages from a quorum), and only *) 92 | (* if it has not done so before. *) 93 | (***************************************************************************) 94 | Phase2a(b, i) == 95 | /\ \neg (\E m \in network : m[1] = "2a" /\ m[2] = i /\ m[3] = b) 96 | /\ \E Q \in Quorums : 97 | /\ \A a \in Q : \E m \in 1bMsgs(b, i, Q) : m[2] = a 98 | /\ LET maxV == MaxVote(b, i , Q) 99 | safe == IF maxV # None THEN {maxV} ELSE propCmds 100 | IN \E v \in safe : network' = network \cup {<<"2a", i, b, v>>} 101 | /\ UNCHANGED <> 102 | 103 | Vote(a, b, i) == 104 | /\ ballot[a] = b 105 | /\ \E m \in network : 106 | /\ m[1] = "2a" /\ m[2] = i /\ m[3] = b 107 | /\ vote' = [vote EXCEPT ![a] = [@ EXCEPT ![i] = 108 | [@ EXCEPT ![b] = m[4]]]] 109 | /\ UNCHANGED <> 110 | 111 | Next == 112 | \/ \E c \in V : Propose(c) 113 | \/ \E b \in Ballots : Phase1a(b) 114 | \/ \E a \in Acceptors, b \in Ballots, v \in V : Phase1b(a, b, v) 115 | \/ \E b \in Ballots, i \in Instances : Phase2a(b, i) 116 | \/ \E a \in Acceptors, b \in Ballots, i \in Instances : Vote(a, b, i) 117 | 118 | Spec == Init /\ [][Next]_<> 119 | 120 | MultiPaxos == INSTANCE MultiPaxos 121 | 122 | THEOREM Spec => MultiPaxos!Spec 123 | 124 | ============================================================================= 125 | \* Modification History 126 | \* Last modified Wed Nov 18 18:41:57 EST 2015 by nano 127 | \* Created Fri Nov 13 17:59:21 EST 2015 by nano 128 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Giuliano Losa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MCDiskPaxos.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MCDiskPaxos ---------------------------- 2 | 3 | EXTENDS Naturals 4 | 5 | CONSTANT 6 | N, \* The number of processes 7 | B \* The number of ballots 8 | 9 | P == 1..N 10 | Ballots == 0..(B-1) 11 | Bals(p) == {b \in Ballots : b % N = p-1} 12 | 13 | CONSTANT NotABallot, V, NotAnInput, defaultInitValue 14 | VARIABLES rs, dblock, pc 15 | VARIABLES toRead, phase, blocksRead 16 | 17 | INSTANCE DiskPaxos 18 | 19 | ============================================================================= 20 | \* Modification History 21 | \* Last modified Fri Jan 13 18:58:55 PST 2017 by nano 22 | \* Created Mon Jan 09 18:08:36 PST 2017 by nano 23 | -------------------------------------------------------------------------------- /MultiConsensus.tla: -------------------------------------------------------------------------------- 1 | --------------------------- MODULE MultiConsensus --------------------------- 2 | (***************************************************************************) 3 | (* A set of constants and definitions for use in the specification of *) 4 | (* MultiPaxos-like algorithms. *) 5 | (***************************************************************************) 6 | 7 | EXTENDS Integers, FiniteSets 8 | 9 | CONSTANTS Acceptors, Quorums, V, None 10 | 11 | ASSUME None \notin V 12 | 13 | ASSUME \A Q \in Quorums : Q \subseteq Acceptors 14 | 15 | ASSUME \A Q1,Q2 \in Quorums : Q1 \cap Q2 # {} 16 | 17 | Ballots == Nat 18 | 19 | ASSUME -1 \notin Ballots 20 | 21 | Instances == Nat 22 | 23 | MajQuorums == {Q \in SUBSET Acceptors : Cardinality(Q) > Cardinality(Acceptors) \div 2} 24 | 25 | Max(xs, LessEq(_,_)) == CHOOSE x \in xs : \A y \in xs : LessEq(y,x) 26 | 27 | ============================================================================= 28 | \* Modification History 29 | \* Last modified Tue Nov 03 10:15:12 EST 2015 by nano 30 | \* Created Mon Nov 02 19:28:17 EST 2015 by nano 31 | -------------------------------------------------------------------------------- /MultiPaxos.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nano-o/MultiPaxos/308ddb36fb5374bd95a0b5bfbeb934926234564d/MultiPaxos.pdf -------------------------------------------------------------------------------- /MultiPaxos.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE MultiPaxos ----------------------------- 2 | 3 | 4 | (***************************************************************************) 5 | (* An abstract specification of the MultiPaxos algorithm. We do not model *) 6 | (* the network nor leaders explicitely. Instead, we keep the history of *) 7 | (* all votes cast and use this history to describe how new votes are cast. *) 8 | (* Note that, in some way, receiving a message corresponds to reading a *) 9 | (* past state of the sender. We produce the effect of having the leader *) 10 | (* by requiring that not two different values can be voted for in the same *) 11 | (* ballot. *) 12 | (* *) 13 | (* This specification is inspired from the abstract specification of *) 14 | (* Generalized Paxos presented in the Generalized Paxos paper by Lamport. *) 15 | (***************************************************************************) 16 | 17 | EXTENDS MultiConsensus 18 | 19 | (***************************************************************************) 20 | (* The variable ballot maps an acceptor to its current ballot. *) 21 | (* *) 22 | (* Given an acceptor a, an instance i, and a ballot b, vote[a][i][b] *) 23 | (* records the vote that a casted in ballot b of instance i. *) 24 | (***************************************************************************) 25 | VARIABLES 26 | ballot, vote, propCmds 27 | 28 | Init == 29 | /\ ballot = [a \in Acceptors |-> -1] 30 | /\ vote = [a \in Acceptors |-> 31 | [i \in Instances |-> 32 | [b \in Ballots |-> None]]] 33 | /\ propCmds = {} 34 | 35 | TypeInv == 36 | /\ ballot \in [Acceptors -> {-1} \cup Ballots] 37 | /\ vote \in [Acceptors -> 38 | [Instances -> 39 | [Ballots -> {None} \cup V]]] 40 | /\ propCmds \in SUBSET V 41 | 42 | 43 | (***************************************************************************) 44 | (* Now starts the specification of the algorithm *) 45 | (***************************************************************************) 46 | 47 | (***************************************************************************) 48 | (* A ballot is conservative when all acceptors which vote in the ballot *) 49 | (* vote for the same value. In MultiPaxos, the leader of a ballot ensures *) 50 | (* that the ballot is conservative. *) 51 | (***************************************************************************) 52 | Conservative(i,b) == 53 | \A a1,a2 \in Acceptors : 54 | LET v1 == vote[a1][i][b] 55 | v2 == vote[a2][i][b] 56 | IN (v1 # None /\ v2 # None) => v1 = v2 57 | 58 | ConservativeVoteArray == 59 | \A i \in Instances : \A b \in Ballots : 60 | Conservative(i,b) 61 | 62 | (***************************************************************************) 63 | (* The maximal ballot smaller than max in which a has voted in instance i. *) 64 | (***************************************************************************) 65 | MaxVotedBallot(i, a, max) == 66 | Max({b \in Ballots : b <= max /\ vote[a][i][b] # None} \cup {-1}, <=) 67 | 68 | MaxVotedBallots(i, Q, max) == {MaxVotedBallot(i, a, max) : a \in Q} 69 | 70 | (***************************************************************************) 71 | (* The vote casted in the maximal ballot smaller than max by an acceptor *) 72 | (* of the quorum Q. *) 73 | (***************************************************************************) 74 | HighestVote(i, max, Q) == 75 | IF \E a \in Q : MaxVotedBallot(i, a, max) # -1 76 | THEN 77 | LET MaxVoter == CHOOSE a \in Q : 78 | MaxVotedBallot(i, a, max) = Max(MaxVotedBallots(i, Q, max), <=) 79 | IN vote[MaxVoter][i][MaxVotedBallot(i, MaxVoter, max)] 80 | ELSE 81 | None 82 | 83 | (***************************************************************************) 84 | (* Values that are safe to vote for in ballot b according to a quorum Q *) 85 | (* whose acceptors have all reached ballot b. *) 86 | (* *) 87 | (* If there is an acceptor in Q that has voted in a ballot less than b, *) 88 | (* then the only safe value is the value voted for by an acceptor in Q in *) 89 | (* the highest ballot less than b. *) 90 | (* *) 91 | (* Else, all values are safe. *) 92 | (* *) 93 | (* In an implementation, the leader of a ballot b can compute *) 94 | (* ProvedSafeAt(i, Q, b) when it receives 1b messages from the quorum Q. *) 95 | (***************************************************************************) 96 | ProvedSafeAt(i, Q, b) == 97 | IF HighestVote(i, b-1, Q) # None 98 | THEN {HighestVote(i, b-1, Q)} 99 | ELSE V 100 | 101 | (***************************************************************************) 102 | (* The propose action: *) 103 | (***************************************************************************) 104 | Propose(v) == 105 | /\ propCmds' = propCmds \cup {v} 106 | /\ UNCHANGED <> 107 | 108 | 109 | (***************************************************************************) 110 | (* The JoinBallot action: an acceptor can join a higher ballot at any *) 111 | (* time. In an implementation, the JoinBallot action is triggered by a 1a *) 112 | (* message from the leader of the new ballot. *) 113 | (***************************************************************************) 114 | JoinBallot(a, b) == 115 | /\ ballot[a] < b 116 | /\ ballot' = [ballot EXCEPT ![a] = b] 117 | /\ UNCHANGED <> 118 | 119 | (***************************************************************************) 120 | (* The Vote action: an acceptor casts a vote in instance i. This action *) 121 | (* is enabled when the acceptor has joined a ballot, has not voted in its *) 122 | (* current ballot, and can determine, by reading the last vote cast by *) 123 | (* each acceptor in a quorum, which value is safe to vote for. If *) 124 | (* multiple values are safe to vote for, we ensure that only one can be *) 125 | (* voted for by requiring that the ballot remain conservative. *) 126 | (* *) 127 | (* In an implementation, the computation of safe values is done by the *) 128 | (* leader of the ballot when it receives 1b messages from a quorum of *) 129 | (* acceptors. The leader then picks a unique value among the safe values *) 130 | (* and suggests it to the acceptors. *) 131 | (***************************************************************************) 132 | Vote(a,i) == 133 | /\ ballot[a] # -1 134 | /\ vote[a][i][ballot[a]] = None 135 | /\ \E Q \in Quorums : 136 | /\ \A q \in Q : ballot[q] \geq ballot[a] 137 | /\ \E v \in ProvedSafeAt(i, Q, ballot[a]) \cap propCmds : 138 | vote' = [vote EXCEPT ![a] = 139 | [@ EXCEPT ![i] = [@ EXCEPT ![ballot[a]] = v]]] 140 | /\ UNCHANGED <> 141 | /\ Conservative(i, ballot[a])' 142 | 143 | Next == 144 | \/ \E v \in V : Propose(v) 145 | \/ \E a \in Acceptors : \E b \in Ballots : JoinBallot(a, b) 146 | \/ \E a \in Acceptors : \E i \in Instances : Vote(a, i) 147 | 148 | Spec == Init /\ [][Next]_<> 149 | 150 | (***************************************************************************) 151 | (* Some properties and invariants that help understanding the algo and *) 152 | (* would probably be needed in a proof. *) 153 | (***************************************************************************) 154 | 155 | (***************************************************************************) 156 | (* The maximal ballot in which an acceptor a voted is always less than or *) 157 | (* equal to its current ballot. *) 158 | (***************************************************************************) 159 | WellFormed == \A a \in Acceptors : \A i \in Instances : \A b \in Ballots : 160 | b > ballot[a] => vote[a][i][b] = None 161 | 162 | THEOREM Spec => []WellFormed 163 | 164 | ChosenAt(i,b,v) == 165 | \E Q \in Quorums : \A a \in Q : vote[a][i][b] = v 166 | 167 | Chosen(i,v) == 168 | \E b \in Ballots : ChosenAt(i,b,v) 169 | 170 | Choosable(v, i, b) == 171 | \E Q \in Quorums : \A a \in Q : ballot[a] > b => vote[a][i][b] = v 172 | 173 | SafeAt(v, i, b) == 174 | \A b2 \in Ballots : \A v2 \in V : 175 | (b2 < b /\ Choosable(v2, i, b2)) 176 | => v = v2 177 | 178 | SafeInstanceVoteArray(i) == \A b \in Ballots : \A a \in Acceptors : 179 | LET v == vote[a][i][b] 180 | IN v # None => SafeAt(v, i, b) 181 | 182 | SafeVoteArray == \A i \in Instances : SafeInstanceVoteArray(i) 183 | 184 | THEOREM Spec => []SafeVoteArray 185 | 186 | (***************************************************************************) 187 | (* If the vote array is well formed and the vote array is safe, then for *) 188 | (* each instance only a unique value can be chosen. *) 189 | (***************************************************************************) 190 | THEOREM TypeInv /\ WellFormed /\ SafeVoteArray => \A i \in Instances : 191 | \A v1,v2 \in V : Chosen(i, v1) /\ Chosen(i, v2) => v1 = v2 192 | 193 | (***************************************************************************) 194 | (* In a well-formed, safe, and conservative vote array, all values that *) 195 | (* are proved safe are safe. *) 196 | (***************************************************************************) 197 | THEOREM TypeInv /\ WellFormed /\ SafeVoteArray /\ ConservativeVoteArray 198 | => \A v \in V : \A i \in Instances : 199 | \A Q \in Quorums : \A b \in Ballots : 200 | /\ \A a \in Q : ballot[a] \geq b 201 | /\ v \in ProvedSafeAt(i, Q, b) 202 | => SafeAt(v, i, b) 203 | Correctness == 204 | \A i \in Instances : \A v1,v2 \in V : 205 | Chosen(i, v1) /\ Chosen(i, v2) => v1 = v2 206 | 207 | THEOREM Spec => []Correctness 208 | 209 | ============================================================================= 210 | \* Modification History 211 | \* Last modified Thu Jan 21 01:21:39 EST 2016 by nano 212 | \* Created Mon Nov 02 09:08:37 EST 2015 by nano 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiPaxos 2 | A formalization of the Multi-Paxos algorithm. 3 | Heavily inspired by a few of Leslie Lamport's papers. 4 | 5 | Also contains a formalization of Disk Paxos in PlusCal. 6 | --------------------------------------------------------------------------------