Vsync paxos
include order
type node
instance epochobj : unbounded_sequence
alias epoch = epochobj.t
instance stakeord : unbounded_sequence
alias stake = stakeord.t
axiom 0 <= X:epoch
axiom X:stake >= 0
axiom ~(X:stake < 0)
axiom X:stake < Y -> Y ~= 0
axiom X:stake = Y -> (X = 0 <-> Y = 0)
axiom X:stake < Y | X ~= 0 | Y = 0
instance inst_object : unbounded_sequence
alias inst = inst_object.t
axiom ~(T:epoch < T)
axiom X:epoch < Y -> Y ~= 0
axiom X:epoch = Y -> (X = 0 <-> Y = 0)
axiom X:epoch < Y | X ~= 0 | Y = 0
type value
type config
relation val_at(C:config, I:inst, V:value)
object nodeset = {
type set
relation member(N:node,S:set)
relation majority(S:set)
function common(S:set,T:set) : node
axiom majority(S) & majority(T) & N = common(S,T) -> member(N,S) & member(N,T)
}
function leader_of(E:epoch) : node
relation wedge_msg(E:epoch, S:stake)
relation wedge_ack_msg(E:epoch, N:node, S1:stake, S2:stake, C:config)
relation accept_msg(E:epoch, S:stake, C:config)
relation accepted_msg(E:epoch, N:node, S:stake, C:config)
relation wedged(E:epoch, N:node)
relation started(E:epoch, N:node) # TODO: add nodes and nodesets
function config_decided(E:epoch) : config # ghost relation
derived relations:
function node_stake(E:epoch, N:node) : stake
relation proposal(E:epoch, I:inst, V:value)
relation vote(E:epoch, N:node, I:inst, V:value)
relation decision(E:epoch, N:node, I:inst, V:value)
relation transferral(E:epoch, N:node, I:inst, V:value)
records past decisions of any epoch or node
relation any_decision(I:inst, V:value)
relation any_epoch_decision(E:epoch, I:inst, V:value)
function dec_maj(I:inst, V:value) : nodeset.set # records majority for decision
function dec_epoch(I:inst, V:value) : epoch
function accept_maj(E:epoch,K:stake) : nodeset.set
function epoch_maj(E:epoch) : nodeset.set # The majority that finished epoch E
relation epoch_ended(E:epoch)
function epoch_stake(E:epoch) : stake
function left_maj(E:epoch,K:stake) : nodeset.set
function accepted_maj(E:epoch) : nodeset.set
after init {
wedge_msg(E,S) := false;
wedge_ack_msg(E,N,S1,S2,C2) := false;
accept_msg(E,S,C2) := false;
accepted_msg(E,N,S,C) := false;
proposal(E,I,V) := false;
vote(E,N,I,V) := false;
decision(E,N,I,V) := false;
any_decision(I,V) := false;
any_epoch_decision(E,I,V) := false;
transferral(E,N,I,V) := false;
node_stake(E,N) := 0;
wedged(E,N) := false;
started(E,N) := E = 0;
epoch_ended(E) := false;
}
a client sends a wedge message:
action send_wedge (e:epoch,s:stake) = { # TODO add nodeset.sets and membershipt
require s ~= 0;
wedge_msg(e,s) := true
}
action join_stake (e:epoch, n:node, s:stake) = {
require s ~= 0;
require started(e,n);
require wedge_msg(e,s);
require node_stake(e,n) < s;
wedged(e,n) := true;
node_stake(e,n) := s;
}
action receive_wedge_ack_msgs(e:epoch, s:stake, nset:nodeset.set, maxs : stake, n: node, c : config) = {
require s ~= 0;
assume forall C. ~accept_msg(e,s,C);
require forall N. nodeset.member(N,nset) -> ~(node_stake(e,N) < s);
require nodeset.majority(nset);
if maxs = 0 {
if no stake, n must have the exact configuration and it must be maximal
require nodeset.member(n,nset) & forall I,V . val_at(c,I,V) <-> (vote(e,n,I,V) | transferral(e,n,I,V));
require forall N,I,V. nodeset.member(N,nset) & (vote(e, N, I, V) | transferral(e,N,I,V)) -> val_at(c, I, V);
require forall N,S,C. 0 < S & S < s & nodeset.member(N, nset) -> ~accepted_msg(e,N,S,C);
accept_maj(e,s) := nset;
} else {
if stake, n must have accepted at the stake and stake must be maximal
require maxs < s & nodeset.member(n, nset) & accepted_msg(e,n,maxs,c);
require forall N,MAXS,C. (MAXS < s & nodeset.member(N, nset) & accepted_msg(e,N,MAXS,C)) -> MAXS <= maxs;
accept_maj(e,s) := accept_maj(e,maxs);
};
Key invariant for stakes: when we create a new stake, all lesser stakes with different configs are
dead. A stake is dead if there is a majority that has left it and not accepted it. The function
left_maj records the majority that witnesses the death of a stake.
left_maj(e,K) := nset if maxs < K & K < s else left_maj(e,K);
assert
0 < K & K < s ->
(accept_msg(e,K,c) | (nodeset.majority(left_maj(e,K)) &
(nodeset.member(N,left_maj(e,K)) ->
K < node_stake(e,N) & ~accepted_msg(e,N,K,C))))
proof let
N1 = n,
K2 = maxs,
E1 = e,
C1 = c,
S1 = left_maj(E1,K);
assert nodeset.majority(accept_maj(e,s)) & (nodeset.member(N,accept_maj(e,s)) ->
~(node_stake(e,N) = 0) & ( (vote(e, N, I, V) | transferral(e,N,I,V)) -> val_at(c, I, V)))
proof let
N1 = n,
K2 = maxs,
E1 = e,
C1 = c,
S1 = accept_maj(e,s),
K = s;
accept_msg(e, s, c) := true;
}
action send_accepted_msg(e:epoch, n:node, s:stake, c:config) = {
require s ~= 0;
require started(e,n);
require ~(s < node_stake(e,n));
require accept_msg(e,s,c);
accepted_msg(e, n, s, c) := true
}
action start(e:epoch, e_prev: epoch, n:node, s:stake, nset:nodeset.set, c:config) = {
require s ~= 0;
require ~started(e,n);
require nodeset.majority(nset);
require e_prev < e & forall E. ~(e_prev < E & E < e);
require epochobj.succ(e_prev, e); # make sure e_prev is the previous epoch deterministically
require forall N. nodeset.member(N,nset) -> accepted_msg(e_prev, N, s, c);
assert epoch_ended(e_prev) -> c = config_decided(e_prev)
proof {
let E1 = e_prev, C1 = c, C2 = config_decided(e_prev), K = s, K2 = epoch_stake(E1), N1 = nodeset.common(nset,nset);
if s < K2 {
let S1 = left_maj(E1,K), S2 = nset, N = nodeset.common(S1,S2)
} else {
let S1 = left_maj(E1,K2), S2 = accepted_maj(E1), N = nodeset.common(S1,S2)
}
};
TODO: is this needed?
assert accept_msg(e_prev,s,c)
proof let
E1 = e_prev,
K = s,
C = c,
N = nodeset.common(nset,nset);
TODO: is this needed?
assert E2 < e_prev -> epoch_ended(E2)
proof let
C = c,
K = s,
E1 = e_prev,
N = nodeset.common(nset,nset);
assert E2 < e_prev & nodeset.member(N1,epoch_maj(E2)) & vote(E2,N1,I,V1) -> val_at(c,I,V1)
proof let
C = c,
K = s,
E1 = e_prev,
S1 = accept_maj(E1,s),
N = nodeset.common(S1,S1),
S2 = epoch_maj(E2),
K2 = epoch_stake(E2),
C2 = config_decided(E2);
config_decided(e_prev) := c;
transferral(e,n,I,V) := val_at(c,I,V);
decision(e,n,I,V) := val_at(c,I,V);
started(e,n) := true;
if ~epoch_ended(e_prev) {
epoch_maj(e_prev) := accept_maj(e_prev,s);
epoch_stake(e_prev) := s;
accepted_maj(e_prev) := nset;
};
epoch_ended(e_prev) := true;
}
the leader proposes a value to vote for at instance i.
action propose(e:epoch, i:inst, v:value) = {
require started(e,leader_of(e));
require forall BIG_I,V. i <= BIG_I -> ~transferral(e,leader_of(e),BIG_I,V);
require forall BIG_I,V. i <= BIG_I -> ~proposal(e,BIG_I,V);
proposal(e,i,v) := true
}
action do_vote(e:epoch, n:node, i:inst, v:value) = {
require started(e, n);
require ~wedged(e,n);
require proposal(e,i,v);
assume ~(i <= I) -> exists V . vote(c,n,I,V);
vote(e, n, i, v) := true
}
action decide(e:epoch, n:node, v:value, nset:nodeset.set, i:inst) = {
require started(e, n);
require nodeset.majority(nset);
require forall N. nodeset.member(N, nset) -> vote(e, N, i, v);
decision(e, n, i, v) := true;
any_decision(i,v) := true;
any_epoch_decision(e,i,v) := true;
dec_maj(i,v) := nset;
dec_epoch(i,v) := e;
assert
~(~(E1 < E2) & any_decision(I,V1) & any_decision(I,V2) & V1 ~= V2 & dec_epoch(I,V1) = E1 & dec_epoch(I,V2) = E2)
proof let
S3 = dec_maj(I,V1),
S2 = dec_maj(I,V2),
S1 = (S2 if E2 = E1 else epoch_maj(E2)),
N = nodeset.common(S1,S2),
K = epoch_stake(E2),
C = config_decided(E2),
N1 = leader_of(E1),
N2 = nodeset.common(S3,S3);
}
export send_wedge
export join_stake
export receive_wedge_ack_msgs
export send_accepted_msg
export start
export propose
export do_vote
export decide
include mc_schemata
instantiate total_order_schemata(stake)
attribute method = mc
attribute separate = true