From Paxos to BFT
This is a sequel to Notes on Paxos post. Similarly, the primarily goal here is for me to understand why the BFT consensus algorithm works in detail. This might, or might not be useful for other people! The Paxos article is a prerequisite, best to read that now, and return to this article tomorrow :)
Note also that while Paxos was more or less a direct translation of Lamport’s lecture, this post is a mish-mash oft the original BFT paper by Liskov and Castro, my own thinking, and a cursory glance as this formalization. As such, the probability that there are no mistakes here is quite low.
What is BFT?
BFT stands for Byzantine Fault Tolerant consensus. Similarly to Paxos, we imagine a distributed system of computers communicating over a faulty network which can arbitrary reorder, delay, and drop messages. And we want computers to agree on some specific choice of value among the set of possibilities, such that any two computers pick the same value. Unlike Paxos though, we also assume that computers themselves might be faulty or malicious. So, we add a new condition to our list of bad things. Besides reordering, duplication, delaying and dropping, a fake message can be manufactured out of thin air.
Of course, if absolutely arbitrary messages can be forged, then no consensus is possible — each machine lives in its own solipsistic world which might be completely unlike the world of every other machine. So there’s one restriction — messages are cryptographically signed by the senders, and it is assumed that it is impossible for a faulty node to impersonate non-faulty one.
Can we still achieve consensus? As long as for each f
faulty, malicious nodes, we have at least 2f + 1
honest
ones.
Similarly to the Paxos post, we will capture this intuition into a precise mathematical statement about trajectories of state machines.
Paxos Revisited
Our plan is to start with vanilla Paxos, and then patch it to allow byzantine behavior. Here’s what we’ve arrived at last time:
Sets:
𝔹 -- Numbered set of ballots (for example, ℕ)
𝕍 -- Arbitrary set of values
𝔸 -- Finite set of acceptors
ℚ ∈ 2^𝔸 -- Set of quorums
-- Sets of messages for each of the four subphases
Msgs1a ≡ {type: {"1a"}, bal: 𝔹}
Msgs1b ≡ {type: {"1b"}, bal: 𝔹, acc: 𝔸,
vote: {bal: 𝔹, val: 𝕍} ∪ {null}}
Msgs2a ≡ {type: {"2a"}, bal: 𝔹, val: 𝕍}
Msgs2b ≡ {type: {"2b"}, bal: 𝔹, val: 𝕍, acc: 𝔸}
Assume:
∀ q1, q2 ∈ ℚ: q1 ∩ q2 ≠ {}
Vars:
-- Set of all messages sent so far
msgs ∈ 2^(Msgs1a ∪ Msgs1b ∪ Msgs2a ∪ Msgs2b)
-- Function that maps acceptors to ballot numbers or -1
-- maxBal :: 𝔸 -> 𝔹 ∪ {-1}
maxBal ∈ (𝔹 ∪ {-1})^𝔸
-- Function that maps acceptors to their last vote
-- lastVote :: 𝔸 -> {bal: 𝔹, val: 𝕍} ∪ {null}
lastVote ∈ ({bal: 𝔹, val: 𝕍} ∪ {null})^𝔸
Send(m) ≡ msgs' = msgs ∪ {m}
Safe(b, v) ≡
∃ q ∈ ℚ:
let
qmsgs ≡ {m ∈ msgs: m.type = "1b" ∧ m.bal = b ∧ m.acc ∈ q}
qvotes ≡ {m ∈ qmsgs: m.vote ≠ null}
in
∀ a ∈ q: ∃ m ∈ qmsgs: m.acc = a
∧ ( qvotes = {}
∨ ∃ m ∈ qvotes:
m.vote.val = v
∧ ∀ m1 ∈ qvotes: m1.vote.bal <= m.vote.bal)
Phase1a(b) ≡
maxBal' = maxBal
∧ lastVote' = lastVote
∧ Send({type: "1a", bal: b})
Phase1b(a) ≡
∃ m ∈ msgs:
m.type = "1a" ∧ maxBal(a) < m.bal
∧ maxBal' = λ a1 ∈ 𝔸: if a = a1
then m.bal - 1
else maxBal(a1)
∧ lastVote' = lastVote
∧ Send({type: "1b", bal: m.bal, acc: a, vote: lastVote(a)})
Phase2a(b, v) ≡
¬∃ m ∈ msgs: m.type = "2a" ∧ m.bal = b
∧ Safe(b, v)
∧ maxBal' = maxBal
∧ lastVote' = lastVote
∧ Send({type: "2a", bal: b, val: v})
Phase2b(a) ≡
∃ m ∈ msgs:
m.type = "2a" ∧ maxBal(a) < m.bal
∧ maxBal' = λ a1 ∈ 𝔸: if a = a1 then m.bal else maxBal(a1)
∧ lastVote' = λ a1 ∈ 𝔸: if a = a1
then {bal: m.bal, val: m.val}
else lastVote(a1)
∧ Send({type: "2b", bal: m.bal, val: m.val, acc: a})
Init ≡
msgs = {}
∧ maxBal = λ a ∈ 𝔸: -1
∧ lastVote = λ a ∈ 𝔸: null
Next ≡
∃ b ∈ 𝔹:
Phase1a(b) ∨ ∃ v ∈ 𝕍: Phase2a(b, v)
∨ ∃ a ∈ 𝔸:
Phase1b(a) ∨ Phase2b(a)
chosen ≡
{v ∈ V: ∃ q ∈ ℚ, b ∈ 𝔹: AllVotedFor(q, b, v)}
AllVotedFor(q, b, v) ≡
∀ a ∈ q: (a, b, v) ∈ votes
votes ≡
let
msgs2b ≡ {m ∈ msgs: m.type = "2b"}
in
{(m.acc, m.bal, m.val): m ∈ msgs2b}
Our general idea is to add some “evil” acceptors 𝔼 to the mix and allow them sending arbitrary messages, while at the same time making sure that the subset of “good” acceptors continues to run Paxos. What makes this complex is that we don’t know which acceptor are good and which are bad. So this is our setup
Sets:
𝔹 -- Numbered set of ballots (for example, ℕ)
𝕍 -- Arbitrary set of values
𝔸 -- Finite set of good acceptors
𝔼 -- Finite set of evil acceptors
𝔸𝔼 ≡ 𝔸 ∪ 𝔼 -- All acceptors
ℚ ∈ 2^𝔸𝔼 -- Set of quorums
Msgs1a ≡ {type: {"1a"}, bal: 𝔹}
Msgs1b ≡ {type: {"1b"}, bal: 𝔹, acc: 𝔸𝔼,
vote: {bal: 𝔹, val: 𝕍} ∪ {null}}
Msgs2a ≡ {type: {"2a"}, bal: 𝔹, val: 𝕍}
Msgs2b ≡ {type: {"2b"}, bal: 𝔹, val: 𝕍, acc: 𝔸𝔼}
Assume:
𝔼 ∩ 𝔸 = {}
∀ q1, q2 ∈ ℚ: q1 ∩ q2 ∩ 𝔸 ≠ {}
If previously the quorum condition was “any two quorums have an
acceptor in common”, it is now “any two quorums have a good acceptor
in common”. An alternative way to say that is “a byzantine quorum is
a super-set of normal quorum”, which corresponds to the intuition
where we are running normal Paxos, and there are just some extra
evil guys whom we try to ignore. For Paxos, we allowed f
faulty out of 2f + 1
total nodes with f+1
quorums. For Byzantine Paxos, we’ll have f
byzantine
out 3f + 1
nodes with 2f+1
quorums. As
I’ve said, if we forget about byzantine folks, we get exactly f + 1
out of 2f + 1
picture of normal Paxos.
The next step is to determine behavior for byzantine nodes. They can send any message, as long as they are the author:
Byzantine(a) ≡
∃ b ∈ 𝔹: Send({type: "1a", bal: b})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2a", bal: b, val: v})
∨ ∃ b1, b2 ∈ 𝔹, v ∈ 𝕍: Send({type: "1b", bal: b1, acc: a,
vote: {bal: b2, val: v}})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2b", bal: b, val: v, acc: a})
∧ maxBal' = maxBal
∧ lastVote' = lastVote
That is, a byzantine acceptor can send any 1a
or 2a
message at any time, while for 1b
and 2b
the author should match.
What breaks? The most obvious thing is Phase2b
, that
is, voting. In Paxos, as soon as an acceptor receives a 2a
message, it votes for it. The correctness of Paxos hinges
on the Safe
check before we send 2a
message, but a Byzantine node can send an arbitrary 2a
.
The solution here is natural: rather than blindly trust 2a
messages, acceptors would themselves double-check the
safety condition, and reject the message if it doesn’t hold:
Phase2b(a) ≡
∃ m ∈ msgs:
m.type = "2a" ∧ maxBal(a) < m.bal
∧ Safe(m.bal, m.val)
∧ maxBal' = λ a1 ∈ 𝔸: if a = a1 then m.bal else maxBal(a1)
∧ lastVote' = λ a1 ∈ 𝔸: if a = a1
then {bal: m.bal, val: m.val}
else lastVote(a1)
∧ Send({type: "2b", bal: m.bal, val: m.val, acc: a})
Implementation wise, this means that, when a coordinator sends a
2a
, it also wants to include 1b
messages
proving the safety of 2a
. But in the spec we can just
assume that all messages are broadcasted, for simplicity. Ideally,
for correct modeling you also want to model how each acceptor learns
new messages, to make sure that negative reasoning about a certain
message not being sent doesn’t creep in, but we’ll avoid
that here.
However, just re-checking safety doesn’t fully solve the problem. It
might be the case that several values are safe at a particular
ballot (indeed, in the first ballot any value is safe), and it is
exactly the job of a coordinator / 2a
message to pick
one value to break the tie. And in our case a byzantine coordinator
can send two 2a
for different valid values.
And here we’ll make the single non-trivial modification to the
algorithm. Like the Safe
condition is at the heart of
Paxos, the Confirmed
condition is the heart here.
So basically we expect a good coordinator to send just one 2a
message, but a bad one can send many. And we want to
somehow distinguish the two cases. One way to do that is to
broadcast ACKs for 2a
among acceptors. If I received a
2a
message, checked that the value therein is safe, and
also know that everyone else received this same 2a
message, I can safely vote for the value.
So we introduce a new message type, 2ac
, which confirms
a valid 2a
message:
Msgs2ac ≡ {type: {"2ac"}, bal: 𝔹, val: 𝕍, acc: 𝔸}
Naturally, evil acceptors can confirm whatever:
Byzantine(a) ≡
∃ b ∈ 𝔹: Send({type: "1a", bal: b})
∨ ∃ b1, b2 ∈ 𝔹, v ∈ 𝕍: Send({type: "1b", bal: b1, acc: a,
vote: {bal: b2, val: v}})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2a", bal: b, val: v})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2ac", bal: b, val: v, acc: a})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2b", bal: b, val: v, acc: a})
∧ maxBal' = maxBal
∧ lastVote' = lastVote
But, if we get a quorum of confirmations, we can be sure that no other value will be confirmed in a given ballot (each good acceptors confirms at most a single message in a ballot (and we need a bit of state for that as well))
Confirmed(b, v) ≡
∃ q ∈ ℚ: ∀ a ∈ q: {type: "2ac", bal: b, val: v, acc: a} ∈ msgs
Putting everything so far together, we get
Sets:
𝔹 -- Numbered set of ballots (for example, ℕ)
𝕍 -- Arbitrary set of values
𝔸 -- Finite set of acceptors
𝔼 -- Finite set of evil acceptors
𝔸𝔼 ≡ 𝔸 ∪ 𝔼 -- Set of all acceptors
ℚ ∈ 2^𝔸𝔼 -- Set of quorums
Msgs1a ≡ {type: {"1a"}, bal: 𝔹}
Msgs1b ≡ {type: {"1b"}, bal: 𝔹, acc: 𝔸,
vote: {bal: 𝔹, val: 𝕍} ∪ {null}}
Msgs2a ≡ {type: {"2a"}, bal: 𝔹, val: 𝕍}
Msgs2ac ≡ {type: {"2ac"}, bal: 𝔹, val: 𝕍, acc: 𝔸}
Msgs2b ≡ {type: {"2b"}, bal: 𝔹, val: 𝕍, acc: 𝔸}
Assume:
𝔼 ∩ 𝔸 = {}
∀ q1, q2 ∈ ℚ: q1 ∩ q2 ∩ 𝔸 ≠ {}
Vars:
-- Set of all messages sent so far
msgs ∈ 2^(Msgs1a ∪ Msgs1b ∪ Msgs2a ∪ Msgs2ac ∪ Msgs2b)
-- Function that maps acceptors to ballot numbers or -1
-- maxBal :: 𝔸 -> 𝔹 ∪ {-1}
maxBal ∈ (𝔹 ∪ {-1})^𝔸
-- Function that maps acceptors to their last vote
-- lastVote :: 𝔸 -> {bal: 𝔹, val: 𝕍} ∪ {null}
lastVote ∈ ({bal: 𝔹, val: 𝕍} ∪ {null})^𝔸
-- Function which maps acceptors to values they confirmed as safe
-- confirm :: (𝔸, 𝔹) -> 𝕍 ∪ {null}
confirm ∈ (𝕍 ∪ {null})^(𝔸 × 𝔹)
Send(m) ≡ msgs' = msgs ∪ {m}
Confirmed(b, v) ≡
∃ q ∈ ℚ: ∀ a ∈ q: {type: "2ac", bal: b, val: v, acc: a} ∈ msgs
Safe(b, v) ≡
∃ q ∈ ℚ:
let
qmsgs ≡ {m ∈ msgs: m.type = "1b" ∧ m.bal = b ∧ m.acc ∈ q}
qvotes ≡ {m ∈ qmsgs: m.vote ≠ null}
in
∀ a ∈ q: ∃ m ∈ qmsgs: m.acc = a
∧ ( qvotes = {}
∨ ∃ m ∈ qvotes:
m.vote.val = v
∧ ∀ m1 ∈ qvotes: m1.vote.bal <= m.vote.bal)
Byzantine(a) ≡
∃ b ∈ 𝔹: Send({type: "1a", bal: b})
∨ ∃ b1, b2 ∈ 𝔹, v ∈ 𝕍: Send({type: "1b", bal: b1, acc: a,
vote: {bal: b2, val: v}})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2a", bal: b, val: v})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2ac", bal: b, val: v, acc: a})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2b", bal: b, val: v, acc: a})
∧ maxBal' = maxBal
∧ lastVote' = lastVote
∧ confirm' = confirm
Phase1b(a) ≡
∃ m ∈ msgs:
m.type = "1a" ∧ maxBal(a) < m.bal
∧ maxBal' = λ a1 ∈ 𝔸: if a = a1
then m.bal - 1
else maxBal(a1)
∧ lastVote' = lastVote
∧ confirm' = confirm
∧ Send({type: "1b", bal: m.bal, acc: a, vote: lastVote(a)})
Phase2ac(a) ≡
∃ m ∈ msgs:
m.type = "2a"
∧ confirm(a, m.bal) = null
∧ Safe(m.bal, m.val)
∧ maxBal' = maxBal
∧ lastVote' = lastVote
∧ confirm' = λ a1 ∈ 𝔸, b1 \in 𝔹:
if a = a1 ∧ b1 = m.bal then m.val else confirm(a1, b1)
∧ Send({type: "2ac", bal: m.bal, val: m.val, acc: a})
Phase2b(a) ≡
∃ b ∈ 𝔹, v ∈ 𝕍:
Confirmed(b, v)
∧ maxBal' = λ a1 ∈ 𝔸: if a = a1 then m.bal else maxBal(a1)
∧ lastVote' = λ a1 ∈ 𝔸: if a = a1
then {bal: m.bal, val: m.val}
else lastVote(a1)
∧ confirm' = confirm
∧ Send({type: "2b", bal: m.bal, val: m.val, acc: a})
Init ≡
msgs = {}
∧ maxBal = λ a ∈ 𝔸: -1
∧ lastVote = λ a ∈ 𝔸: null
∧ confirm = λ a ∈ 𝔸, b ∈ 𝔹: null
Next ≡
∃ a ∈ 𝔸:
Phase1b(a) ∨ Phase2ac(a) ∨ Phase2b(a)
∨ ∃ a ∈ 𝔼:
Byzantine(a)
chosen ≡
{v ∈ V: ∃ q ∈ ℚ, b ∈ 𝔹: AllVotedFor(q, b, v)}
AllVotedFor(q, b, v) ≡
∀ a ∈ q: (a, b, v) ∈ votes
votes ≡
let
msgs2b ≡ {m ∈ msgs: m.type = "2b"}
in
{(m.acc, m.bal, m.val): m ∈ msgs2b}
In the above, I’ve also removed phases 1a
and 2a
, as byzantine acceptors are allowed to send arbitrary
messages as well (we’ll need explicit 1a
/2a
for liveness, but we won’t discuss that here).
The most important conceptual addition is Phase2ac
— if
an acceptor receives a new 2a
message for some ballot
with a safe value, it sends out the confirmation provided that it
hadn’t done that already. In Phase2b
then we can vote
for confirmed values: confirmation by a quorum guarantees both that
the value is safe at this ballot, and that this is a single value
that can be voted for in this ballot (two different values can’d be
confirmed in the same ballot, because quorums have an honest
acceptor in common). This almost works, but there’s still a
problem. Can you spot it?
The problem is in the Safe
condition. Recall that the
goal of the Safe
condition is to pick a value v
for ballot b
, such that, if any earlier
ballot b1
concludes, the value chosen in b1
would necessary be v
. The way Safe
works
for ballot b
in normal Paxos is that the coordinator
asks a certain quorum to abstain from further voting in ballots
earlier than b
, collects existing votes, and uses those
votes to pick a safe value. Specifically, it looks at the vote for
the highest-numbered ballot in the set, and declares a value from it
as safe (it is safe: it was safe at that ballot,
and for all future ballots there’s a quorum which abstained from
voting).
This procedure puts a lot of trust in that highest vote, which makes
it vulnerable. An evil acceptor can just say that it voted in some
high ballot, and force a choice of arbitrary value. So, we need some
independent confirmation that the vote was cast for a safe value.
And we can re-use 2ac
messages for this:
Safe(b, v) ≡
∃ q ∈ Q:
let
qmsgs ≡ {m ∈ msgs: m.type = "1b" ∧ m.bal = b ∧ m.acc ∈ q}
qvotes ≡ {m ∈ qmsgs: m.vote ≠ null}
in
∀ a ∈ q: ∃ m ∈ qmsgs: m.acc = a
∧ ( qvotes = {}
∨ ∃ m ∈ qvotes:
m.vote.val = v
∧ ∀ m1 ∈ qvotes: m1.vote.bal <= m.vote.bal
∧ Confirmed(m.vote.bal, v))
And … that’s it, really. Now we can sketch a proof that this thing indeed achieves BFT consensus, because it actually models normal Paxos among non-byzantine acceptors.
Phase1a messages of Paxos are modeled by Phase1a messages of BFT Paxos, as they don’t have any preconditions, the same goes for Phase1b. Phase2a message of Paxos is emitted when a value becomes confirmed in BFT Paxos. This is correct modeling, because BFT’s Safe condition models normal Paxos Safe condition (this … is a bit inexact I think, to make this exact, we want to separate “this value is safe” from “we are voting for this value” in original Paxos as well). Finally, Phase2b also displays direct correspondence.
As a final pop-quiz, I claim that the Confirmed(m.vote.bal,
v)
condition in Safe
above can be relaxed. As
stated, Confirmed
needs a byzantine quorum of
confirmations, which guarantees both that the value is safe and that
it is the single confirmed value, which is a bit more than we need
here. Do you see what would be enough?
The final specification contains this relaxation:
Sets:
𝔹 -- Numbered set of ballots (for example, ℕ)
𝕍 -- Arbitrary set of values
𝔸 -- Finite set of acceptors
𝔼 -- Finite set of evil acceptors
𝔸𝔼 ≡ 𝔸 ∪ 𝔼 -- Set of all acceptors
ℚ ∈ 2^𝔸𝔼 -- Set of quorums
𝕎ℚ ∈ 2^𝔸𝔼 -- Set of weak quorums
Msgs1a ≡ {type: {"1a"}, bal: 𝔹}
Msgs1b ≡ {type: {"1b"}, bal: 𝔹, acc: 𝔸𝔼,
vote: {bal: 𝔹, val: 𝕍} ∪ {null}}
Msgs2a ≡ {type: {"2a"}, bal: 𝔹, val: 𝕍}
Msgs2ac ≡ {type: {"2ac"}, bal: 𝔹, val: 𝕍, acc: 𝔸𝔸𝔼}
Msgs2b ≡ {type: {"2b"}, bal: 𝔹, val: 𝕍, acc: 𝔸𝔸𝔼}
Assume:
𝔼 ∩ 𝔸 = {}
∀ q1, q2 ∈ ℚ: q1 ∩ q2 ∩ 𝔸 ≠ {}
∀ q ∈ 𝕎ℚ: q ∩ 𝔸 ≠ {}
Vars:
-- Set of all messages sent so far
msgs ∈ 2^(Msgs1a ∪ Msgs1b ∪ Msgs2a ∪ Msgs2ac ∪ Msgs2b)
-- Function that maps acceptors to ballot numbers or -1
-- maxBal :: 𝔸 -> 𝔹 ∪ {-1}
maxBal ∈ (𝔹 ∪ {-1})^𝔸
-- Function that maps acceptors to their last vote
-- lastVote :: 𝔸 -> {bal: 𝔹, val: 𝕍} ∪ {null}
lastVote ∈ ({bal: 𝔹, val: 𝕍} ∪ {null})^𝔸
-- Function which maps acceptors to values they confirmed as safe
-- confirm :: (𝔸, 𝔹) -> 𝕍 ∪ {null}
confirm ∈ (𝕍 ∪ {null})^(𝔸 × 𝔹)
Send(m) ≡ msgs' = msgs ∪ {m}
Safe(b, v) ≡
∃ q ∈ ℚ:
let
qmsgs ≡ {m ∈ msgs: m.type = "1b" ∧ m.bal = b ∧ m.acc ∈ q}
qvotes ≡ {m ∈ qmsgs: m.vote ≠ null}
in
∀ a ∈ q: ∃ m ∈ qmsgs: m.acc = a
∧ ( qvotes = {}
∨ ∃ m ∈ qvotes:
m.vote.val = v
∧ ∀ m1 ∈ qvotes: m1.vote.bal <= m.vote.bal
∧ confirmedWeak(m.vote.val, v))
Confirmed(b, v) ≡
∃ q ∈ ℚ: ∀ a ∈ q: {type: "2ac", bal: b, val: v, acc: a} ∈ msgs
ConfirmedWeak(b, v) ≡
∃ q ∈ 𝕎ℚ: ∀ a ∈ q: {type: "2ac", bal: b, val: v, acc: a} ∈ msgs
Byzantine(a) ≡
∃ b ∈ 𝔹: Send({type: "1a", bal: b})
∨ ∃ b1, b2 ∈ 𝔹, v ∈ 𝕍: Send({type: "1b", bal: b1, acc: a,
vote: {bal: b2, val: v}})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2a", bal: b, val: v})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2ac", bal: b, val: v, acc: a})
∨ ∃ b ∈ 𝔹, v ∈ 𝕍: Send({type: "2b", bal: b, val: v, acc: a})
∧ maxBal' = maxBal
∧ lastVote' = lastVote
∧ confirm' = confirm
Phase1b(a) ≡
∃ m ∈ msgs:
m.type = "1a" ∧ maxBal(a) < m.bal
∧ maxBal' = λ a1 ∈ 𝔸: if a = a1
then m.bal - 1
else maxBal(a1)
∧ lastVote' = lastVote
∧ confirm' = confirm
∧ Send({type: "1b", bal: m.bal, acc: a, vote: lastVote(a)})
Phase2ac(a) ≡
∃ m ∈ msgs:
m.type = "2a"
∧ confirm(a, m.bal) = null
∧ Safe(m.bal, m.val)
∧ maxBal' = maxBal
∧ lastVote' = lastVote
∧ confirm' = λ a1 ∈ 𝔸, b1 \in 𝔹:
if a = a1 ∧ b1 = m.bal then m.val else confirm(a1, b1)
∧ Send({type: "2ac", bal: m.bal, val: m.val, acc: a})
Phase2b(a) ≡
∃ b ∈ 𝔹, v ∈ 𝕍:
confirmed(b, v)
∧ maxBal' = λ a1 ∈ 𝔸: if a = a1 then m.bal else maxBal(a1)
∧ lastVote' = λ a1 ∈ 𝔸: if a = a1
then {bal: m.bal, val: m.val}
else lastVote(a1)
∧ confirm' = confirm
∧ Send({type: "2b", bal: m.bal, val: m.val, acc: a})
Init ≡
msgs = {}
∧ maxBal = λ a ∈ 𝔸: -1
∧ lastVote = λ a ∈ 𝔸: null
∧ confirm = λ a ∈ 𝔸, b ∈ 𝔹: null
Next ≡
∃ b ∈ 𝔹:
Phase1a(b) ∨ ∃ v ∈ 𝕍: Phase2a(b, v)
∨ ∃ a ∈ 𝔸:
Phase1b(a) ∨ Phase2ac(a) ∨ Phase2b(a)
∨ ∃ a ∈ 𝔼:
Byzantine(a)
chosen ≡
{v ∈ V: ∃ q ∈ ℚ, b ∈ 𝔹: AllVotedFor(q, b, v)}
AllVotedFor(q, b, v) ≡
∀ a ∈ q: (a, b, v) ∈ votes
votes ≡
let
msgs2b ≡ {m ∈ msgs: m.type = "2b"}
in
{(m.acc, m.bal, m.val): m ∈ msgs2b}
TLA+ specs for this post are available here: https://github.com/matklad/paxosnotes.