Theory Voting
section ‹The Voting Model›
theory Voting imports Refinement Consensus_Misc Quorums
begin
subsection ‹Model definition›
record v_state =
next_round :: round
votes :: "round ⇒ (process, val) map"
decisions :: "(process, val)map"
text ‹Initially, no rounds have been executed (the next round is 0), no votes have been
cast, and no decisions have been made.›
definition v_init :: "v_state set" where
"v_init = { ⦇ next_round = 0, votes = λr a. None, decisions = Map.empty ⦈ }"
context quorum_process begin
definition quorum_for :: "process set ⇒ val ⇒ (process, val)map ⇒ bool" where
quorum_for_def':
"quorum_for Q v v_f ≡ Q ∈ Quorum ∧ v_f ` Q = {Some v}"
text ‹The following definition of @{term quorum_for} is easier to reason about in Isabelle.›
lemma quorum_for_def:
"quorum_for Q v v_f = (Q ∈ Quorum ∧ (∀p ∈ Q. v_f p = Some v))"
by(auto simp add: quorum_for_def' image_def dest: quorum_non_empty)
definition locked_in_vf :: "(process, val)map ⇒ val ⇒ bool" where
"locked_in_vf v_f v ≡ ∃Q. quorum_for Q v v_f"
definition locked_in :: "v_state ⇒ round ⇒ val ⇒ bool" where
"locked_in s r v = locked_in_vf (votes s r) v"
definition d_guard :: "(process ⇒ val option) ⇒ (process ⇒ val option) ⇒ bool" where
"d_guard r_decisions r_votes ≡ ∀p v.
r_decisions p = Some v ⟶ locked_in_vf r_votes v"
definition no_defection :: "v_state ⇒ (process, val)map ⇒ round ⇒ bool" where
no_defection_def':
"no_defection s r_votes r ≡
∀r' < r. ∀Q ∈ Quorum. ∀v. (votes s r') ` Q = {Some v} ⟶ r_votes ` Q ⊆ {None, Some v}"
text ‹The following definition of @{term no_defection} is easier to reason about in Isabelle.›
lemma no_defection_def:
"no_defection s round_votes r =
(∀r' < r. ∀a Q v. quorum_for Q v (votes s r') ∧ a ∈ Q ⟶ round_votes a ∈ {None, Some v})"
apply(auto simp add: no_defection_def' Ball_def quorum_for_def')
apply(blast)
by (metis option.discI option.inject)
definition locked :: "v_state ⇒ val set" where
"locked s = {v. ∃r. locked_in s r v}"
text ‹The sole system event.›
definition v_round :: "round ⇒ (process, val)map ⇒ (process, val)map ⇒ (v_state × v_state) set" where
"v_round r r_votes r_decisions = {(s, s').
r = next_round s
∧ no_defection s r_votes r
∧ d_guard r_decisions r_votes
∧
s' = s⦇
next_round := Suc r,
votes := (votes s)(r := r_votes),
decisions := (decisions s) ++ r_decisions
⦈
}"
lemmas v_evt_defs = v_round_def
definition v_trans :: "(v_state × v_state) set" where
"v_trans = (⋃r v_f d_f. v_round r v_f d_f) ∪ Id"
definition v_TS :: "v_state TS" where
"v_TS = ⦇ init = v_init, trans = v_trans ⦈"
lemmas v_TS_defs = v_TS_def v_init_def v_trans_def
subsection ‹Invariants›
text ‹The only rounds where votes could have been cast are the ones
preceding the next round.›
definition Vinv1 where
"Vinv1 = {s. ∀r. next_round s ≤ r ⟶ votes s r = Map.empty }"
lemmas Vinv1I = Vinv1_def [THEN setc_def_to_intro, rule_format]
lemmas Vinv1E [elim] = Vinv1_def [THEN setc_def_to_elim, rule_format]
lemmas Vinv1D = Vinv1_def [THEN setc_def_to_dest, rule_format]
text ‹The votes cast must respect the @{term no_defection} property.›
definition Vinv2 where
"Vinv2 = {s. ∀r. no_defection s (votes s r) r }"
lemmas Vinv2I = Vinv2_def [THEN setc_def_to_intro, rule_format]
lemmas Vinv2E [elim] = Vinv2_def [THEN setc_def_to_elim, rule_format]
lemmas Vinv2D = Vinv2_def [THEN setc_def_to_dest, rule_format]
definition Vinv3 where
"Vinv3 = {s. ran (decisions s) ⊆ locked s}"
lemmas Vinv3I = Vinv3_def [THEN setc_def_to_intro, rule_format]
lemmas Vinv3E [elim] = Vinv3_def [THEN setc_def_to_elim, rule_format]
lemmas Vinv3D = Vinv3_def [THEN setc_def_to_dest, rule_format]
subsubsection ‹Proofs of invariants›
lemma Vinv1_v_round:
"{Vinv1} v_round r v_f d_f {> Vinv1}"
by(auto simp add: PO_hoare_defs v_round_def intro!: Vinv1I)
lemmas Vinv1_event_pres = Vinv1_v_round
lemma Vinv1_inductive:
"init v_TS ⊆ Vinv1"
"{Vinv1} trans v_TS {> Vinv1}"
apply (simp add: v_TS_defs Vinv1_def)
by (auto simp add: v_TS_defs Vinv1_event_pres)
lemma Vinv1_invariant: "reach v_TS ⊆ Vinv1"
by (rule inv_rule_basic, auto intro!: Vinv1_inductive)
text ‹The following two lemmas will be useful later, when we
start taking votes with the maximum timestamp.›
lemma Vinv1_finite_map_graph:
"s ∈ Vinv1 ⟹ finite (map_graph (case_prod (votes s)))"
apply(rule finite_dom_finite_map_graph)
apply(rule finite_subset[where B="{0..< v_state.next_round s} × UNIV"])
apply(auto simp add: Vinv1_def dom_def not_le[symmetric])
done
lemma Vinv1_finite_vote_set:
"s ∈ Vinv1 ⟹ finite (vote_set (votes s) Q)"
apply(drule Vinv1_finite_map_graph)
apply(clarsimp simp add: map_graph_def fun_graph_def vote_set_def)
apply(erule finite_surj[where f="λ((r, a), v). (r, v)"])
by(force simp add: image_def)
lemma process_mru_map_add:
assumes
"s ∈ Vinv1"
shows
"process_mru ((votes s)(next_round s := v_f)) =
(process_mru (votes s) ++ (λp. map_option (Pair (next_round s)) (v_f p)))"
proof-
from assms[THEN Vinv1D] have empty: "∀r' ≥ next_round s. votes s r' = Map.empty"
by simp
show ?thesis
by(auto simp add: process_mru_new_votes[OF empty] map_add_def split: option.split)
qed
lemma no_defection_empty:
"no_defection s Map.empty r'"
by(auto simp add: no_defection_def)
lemma no_defection_preserved:
assumes
"s ∈ Vinv1"
"r = next_round s"
"no_defection s v_f r"
"no_defection s (votes s r') r'"
"votes s' = (votes s)(r := v_f)"
shows
"no_defection s' (votes s' r') r'" using assms
by(force simp add: no_defection_def)
lemma Vinv2_v_round:
"{Vinv2 ∩ Vinv1} v_round r v_f d_f {> Vinv2}"
apply(auto simp add: PO_hoare_defs intro!: Vinv2I)
apply(rename_tac s' r' s)
apply(erule no_defection_preserved)
apply(auto simp add: v_round_def intro!: v_state.equality)
done
lemmas Vinv2_event_pres = Vinv2_v_round
lemma Vinv2_inductive:
"init v_TS ⊆ Vinv2"
"{Vinv2 ∩ Vinv1} trans v_TS {> Vinv2}"
apply(simp add: v_TS_defs Vinv2_def no_defection_def)
by (auto simp add: v_TS_defs Vinv2_event_pres)
lemma Vinv2_invariant: "reach v_TS ⊆ Vinv2"
by (rule inv_rule_incr, auto intro: Vinv2_inductive Vinv1_invariant del: subsetI)
lemma locked_preserved:
assumes
"s ∈ Vinv1"
"r = next_round s"
"votes s' = (votes s)(r := v_f)"
shows
"locked s ⊆ locked s'" using assms
apply(auto simp add: locked_def locked_in_def locked_in_vf_def quorum_for_def dest!: Vinv1D)
by (metis option.distinct(1))
lemma Vinv3_v_round:
"{Vinv3 ∩ Vinv1} v_round r v_f d_f {> Vinv3}"
proof(clarsimp simp add: PO_hoare_defs, intro Vinv3I, safe)
fix s s' v
assume step: "(s, s') ∈ v_round r v_f d_f" and inv3: "s ∈ Vinv3" and inv1: "s ∈ Vinv1"
and dec: "v ∈ ran (decisions s')"
have "locked s ⊆ locked s'" using step
by(intro locked_preserved[OF inv1, where s'=s']) (auto simp add: v_round_def)
with Vinv3D[OF inv3] step dec
show "v ∈ locked s'"
apply(auto simp add: v_round_def dest!: ran_map_addD)
apply(auto simp add: locked_def locked_in_def d_guard_def ran_def)
done
qed
lemmas Vinv3_event_pres = Vinv3_v_round
lemma Vinv3_inductive:
"init v_TS ⊆ Vinv3"
"{Vinv3 ∩ Vinv1} trans v_TS {> Vinv3}"
apply(simp add: v_TS_defs Vinv3_def no_defection_def)
by (auto simp add: v_TS_defs Vinv3_event_pres)
lemma Vinv3_invariant: "reach v_TS ⊆ Vinv3"
by (rule inv_rule_incr, auto intro: Vinv3_inductive Vinv1_invariant del: subsetI)
subsection ‹Agreement and stability›
text ‹Only a singe value can be locked within the votes for one round.›
lemma locked_in_vf_same:
"⟦ locked_in_vf v_f v; locked_in_vf v_f w ⟧ ⟹ v = w" using qintersect
apply(auto simp add: locked_in_vf_def quorum_for_def image_iff)
by (metis Int_iff all_not_in_conv option.inject)
text ‹In any reachable state, no two different values can be locked in
different rounds.›
theorem locked_in_different:
assumes
"s ∈ Vinv2"
"locked_in s r1 v"
"locked_in s r2 w"
"r1 < r2"
shows
"v = w"
proof-
from assms(2-3) obtain Q1 Q2
where Q12: "Q1 ∈ Quorum" "Q2 ∈ Quorum" "quorum_for Q1 v (votes s r1)" "quorum_for Q2 w (votes s r2)"
by(auto simp add: locked_in_def locked_in_vf_def quorum_for_def)
then obtain a where "a ∈ Q1" "votes s r2 a = Some w"
using qintersect[OF ‹Q1 ∈ Quorum› ‹Q2 ∈ Quorum›]
by(auto simp add: quorum_for_def)
thus ?thesis using ‹s ∈ Vinv2› ‹quorum_for Q1 v (votes s r1)› ‹r1 < r2›
by(fastforce simp add: Vinv2_def no_defection_def quorum_for_def')
qed
text ‹It is simple to extend the previous theorem to any two (not necessarily different) rounds.›
theorem locked_unique:
assumes
"s ∈ Vinv2"
"v ∈ locked s" "w ∈ locked s"
shows
"v = w"
proof -
from assms(2-3) obtain r1 r2 where quoIn: "locked_in s r1 v" "locked_in s r2 w"
by (auto simp add: locked_def)
have "r1 < r2 ∨ r1 = r2 ∨ r2 < r1" by (rule linorder_less_linear)
thus ?thesis
proof (elim disjE)
assume "r1 = r2"
with quoIn show ?thesis
by(simp add: locked_in_def locked_in_vf_same)
qed(auto intro: locked_in_different[OF ‹s ∈ Vinv2›] quoIn sym)
qed
text ‹We now prove that decisions are stable; once a process makes a decision, it never
changes it, and it does not go back to an undecided state. Note that behaviors grow at
the front; hence @{term "tr ! (i-j)"} is later in the trace than @{term "tr ! i"}.›
lemma stable_decision:
assumes beh: "tr ∈ beh v_TS"
and len: "i < length tr"
and s: "s = nth tr i"
and t: "t = nth tr (i - j)"
and dec:
"decisions s p = Some v"
shows
"decisions t p = Some v"
proof-
have reach: "s ∈ reach v_TS" "t ∈ reach v_TS" using beh s t len
apply(simp_all add: reach_equiv_beh_states)
apply (metis len nth_mem)
apply (metis less_imp_diff_less nth_mem)
done
hence invs2: "s ∈ Vinv2" and invs3: "s ∈ Vinv3"
by(blast dest: Vinv2_invariant[THEN subsetD] Vinv3_invariant[THEN subsetD])+
show ?thesis using t
proof(induction j arbitrary: t)
case (Suc j)
hence dec_j: "decisions (tr ! (i - j)) p = Some v"
by simp
thus "decisions t p = Some v" using Suc
proof(cases "i ≤ j")
case False
define t' where "t' = tr ! (i - j)"
hence "t' ∈ reach v_TS" "t ∈ reach v_TS" using beh len Suc
by (metis beh_in_reach less_imp_diff_less nth_mem)+
hence invs: "t' ∈ Vinv1" "t' ∈ Vinv3" "t ∈ Vinv2" "t ∈ Vinv3"
by(blast dest: Vinv1_invariant[THEN subsetD] Vinv2_invariant[THEN subsetD]
Vinv3_invariant[THEN subsetD])+
hence locked_v: "v ∈ locked t'" using Suc
by(auto simp add: t'_def intro: ranI)
have "i - j = Suc (i - (Suc j))" using False
by simp
hence trans: "(t', t) ∈ trans v_TS" using beh len Suc
by(auto simp add: t'_def intro!: beh_consecutive_in_trans)
hence locked_v_t: "v ∈ locked t" using locked_v
by(auto simp add: v_TS_defs v_round_def
intro: locked_preserved[OF invs(1), THEN subsetD, OF _ _ locked_v])
from trans obtain w where "decisions t p = Some w" using dec_j
by(fastforce simp add: t'_def v_TS_defs v_round_def
split: option.split option.split_asm)
thus ?thesis using invs(4)[THEN Vinv3D] locked_v_t locked_unique[OF invs(3)]
by (metis contra_subsetD ranI)
qed(auto)
next
case 0
thus "decisions t p = Some v" using assms
by auto
qed
qed
text ‹Finally, we prove that the Voting model ensures agreement. Without a loss
of generality, we assume that ‹t› preceeds ‹s› in the trace.›
lemma Voting_agreement:
assumes beh: "tr ∈ beh v_TS"
and len: "i < length tr"
and s: "s = nth tr i"
and t: "t = nth tr (i - j)"
and dec:
"decisions s p = Some v"
"decisions t q = Some w"
shows "w = v"
proof-
have reach: "s ∈ reach v_TS" using beh s t len
apply(simp_all add: reach_equiv_beh_states)
by (metis nth_mem)
hence invs2: "s ∈ Vinv2" and invs3: "s ∈ Vinv3"
by(blast dest: Vinv2_invariant[THEN subsetD] Vinv3_invariant[THEN subsetD])+
thus ?thesis using assms
proof(induction j arbitrary: t)
case 0
hence
"v ∈ locked (tr ! i)"
"w ∈ locked (tr ! i)"
by(auto intro: ranI)
thus ?thesis using invs2 using assms 0
by(auto dest: locked_unique)
next
case (Suc j)
thus ?thesis
proof(cases "i ≤ j")
case False
have dec_t: "decisions t p = Some v" using Suc
by(auto intro: stable_decision[OF beh len s ])
have "t ∈ reach v_TS" using beh len Suc
by (metis beh_in_reach less_imp_diff_less nth_mem)
hence invs: "t ∈ Vinv2" "t ∈ Vinv3"
by(blast dest: Vinv2_invariant[THEN subsetD] Vinv3_invariant[THEN subsetD])+
from dec_t have "v ∈ locked t" using invs(2)
by(auto intro: ranI)
moreover have locked_w_t: "w ∈ locked t" using Suc ‹t ∈ Vinv3›[THEN Vinv3D]
by(auto intro: ranI)
ultimately show ?thesis using locked_unique[OF ‹t ∈ Vinv2›]
by blast
qed(auto)
qed
qed
end
end