Here are four versions of Paxos, three believed correct and one known broken.
persistent state
proposer: n_p (initially 0) --- the latest proposal round
acceptor: n_l (initially 0) --- the largest round received in a prepare()
acceptor: n_a (initially 0) --- the largest round received in an accept()
acceptor: v_a (initially NULL) --- the value received by the accept() for n_a
volatile state at proposer
a --- number of responses received
n_o --- the largest round received in a prepared()
v_o --- the value received by the prepared() for n_o
propose: (at proposer)
n_p := n_p + 1 + uniqueifier()
a := 0 // volatile state
n_o := 0
send prepare(n_p) to a majority of acceptors
receive prepare(n): (at acceptor)
n_l := max(n_l, n)
send prepared(n_a, v_a) to proposer
receive prepared(n, v): (at proposer)
if n > n_o:
n_o := n
v_o := v
a := a + 1
if a = f + 1:
if v_o = NULL:
v_o := choose value
a := 0
n_p := max(n_p, n_o)
send accept(n_p, v_o) to a majority of acceptors
receive accept(n, v): (at acceptor)
if n >= n_l:
n_l := n_a := n
v_a := v
send accepted(n_a) to proposer
receive accepted(n): (at proposer)
if n = n_p:
a := a + 1
if a = f + 1:
send decided(v_o) to all
timeout: (at proposer)
call propose() again
In Bad Paxos, we do not store n_l; instead we overload n_a and make n_a := max(n_l, n_a). This implementation is incorrect. Can you design an example execution that demonstrates this incorrectness?
persistent state
proposer: n_p (initially 0) --- the latest proposal round
acceptor: n_a (initially 0) --- the largest round received in an
accept() or prepare()
acceptor: v_a (initially NULL) --- the value received by the accept() for n_a
volatile state at proposer
a --- number of responses received
n_o --- the largest round received in a prepared()
v_o --- the value received by the prepared() for n_o
propose: (at proposer)
n_p := n_p + 1 + uniqueifier()
a := 0 // volatile state
n_o := 0
send prepare(n_p) to a majority of acceptors
receive prepare(n): (at acceptor)
n_a := max(n_a, n)
send prepared(n_a, v_a) to proposer
receive prepared(n, v): (at proposer)
n_o := max(n_o, n)
if n = n_o and v != NULL:
v_o := v
a := a + 1
if a = f + 1:
if v_o = NULL:
v_o := choose value
a := 0
send accept(n_o, v_o) to a majority of acceptors
receive accept(n, v): (at acceptor)
if n >= n_a:
n_a := n
v_a := v
send accepted(n_a) to proposer
receive accepted(n): (at proposer)
if n = n_o:
a := a + 1
if a = f + 1:
send decided(v_o) to all
timeout: (at proposer)
call propose() again
Many implementations of the Paxos idea are possible, and differences among them are useful to consider. Here’s an alternate choice, Paxos-B, which is closer to Robert Morris’s pseudocode.
persistent state
proposer/acceptor: n_l (initially 0) --- the largest round received in a prepare()
acceptor: n_a (initially 0) --- the largest round received in an accept()
acceptor: v_a (initially NULL) --- the value received by the accept() for n_a
volatile state at proposer
n_p --- round of current proposal
a --- number of prepared/accepted messages received
n_o --- latest known prepared round
v_o --- latest known prepared value
propose: (at proposer)
n_p := n_l + 1 + uniqueifier() // overlap between proposer state and acceptor state
a := 0
n_o := 0
send prepare(n_p) to a majority of acceptors, *including self*
receive prepare(n): (at acceptor)
if n > n_l:
n_l := n
send prepared(n_a, v_a) to acceptor
receive prepared(n, v): (at proposer)
if n > n_o:
n_o := n
v_o := v
a := a + 1
if a = f + 1:
if v_o = NULL:
v_o := choose value
a := 0
send accept(n_p, v_o) to majority of servers
receive accept(n, v): (at acceptor)
if n >= n_l:
n_l := n_a := n
v_a := v
send accepted(n) to proposer
receive accepted(n): (at proposer)
a := a + 1
if a = f + 1:
send decided(v_o) to all
Questions:
In this Paxos implementation, the proposer doesn’t pre-select a majority; instead, it sends prepare and accept messages to all acceptors, and takes the first majority that arrives. As a result, the pseudocode must handle prepared messages that arrive from stragglers while the protocol is accepting.
persistent state
proposer/acceptor: n_l (initially 0) --- the largest round received in a prepare()
acceptor: n_a (initially 0) --- the largest round received in an accept()
acceptor: v_a (initially NULL) --- the value received by the accept() for n_a
volatile state
proposer: n_p --- round of current proposal
proposer: a --- number of prepared/accepted messages received
proposer: n_o --- latest known prepared round
proposer: v_o --- latest known prepared value
propose: (at proposer)
n_p := n_l := n_l + 1 + uniqueifier() // n_p is volatile; overlap between proposer state and acceptor state
a := 1
n_o := 0
send prepare(n_p) to all acceptors except self
receive prepare(n): (at acceptor)
if n > n_l:
n_l := n
send prepared(n_a, v_a) to acceptor
receive prepared(n, v): (at proposer)
if n_p = n_l and n_p ≠ n_a: // a straggler’s prepared will arrive when n_p = n_a already
if n > n_o:
n_o := n
v_o := v
a := a + 1
if a = f + 1:
if v_o = NULL:
v_o := choose value
a := 1
n_a := n_p
v_a := v_o
send accept(n_p, v_o) to all acceptors except self
receive accept(n, v): (at acceptor)
if n >= n_l:
n_l := n_a := n
v_a := v
send accepted(n) to proposer
receive accepted(n): (at proposer)
if n_p = n:
a := a + 1
if a = f + 1:
send decided(v_o) to all
Questions: