forked from elastic/elasticsearch-formal-models
-
Notifications
You must be signed in to change notification settings - Fork 0
/
OneSlot.thy
127 lines (102 loc) · 6.5 KB
/
OneSlot.thy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
section \<open>One-slot consistency\<close>
text \<open>The replicated state machine determines the values that are committed in each of a sequence
of \textit{slots}. Each slot runs a logically-separate consensus algorithm which is shown to be
consistent here. Further below, the protocol is shown to refine this slot-by-slot model correctly.\<close>
text \<open>Consistency is shown to follow from the invariants listed below. Further below, the protocol
is shown to preserve these invariants in each step, which means it is not enormously important
to understand these in detail.\<close>
theory OneSlot
imports Preliminaries
begin
locale oneSlot =
(* basic functions *)
fixes Q :: "Node set set"
fixes v :: "Term \<Rightarrow> Value"
(* message-sent predicates *)
fixes promised\<^sub>f :: "Node \<Rightarrow> Term \<Rightarrow> bool"
fixes promised\<^sub>b :: "Node \<Rightarrow> Term \<Rightarrow> Term \<Rightarrow> bool"
fixes proposed :: "Term \<Rightarrow> bool"
fixes accepted :: "Node \<Rightarrow> Term \<Rightarrow> bool"
fixes committed :: "Term \<Rightarrow> bool"
(* other definitions *)
fixes promised :: "Node \<Rightarrow> Term \<Rightarrow> bool"
defines "promised n t \<equiv> promised\<^sub>f n t \<or> (\<exists> t'. promised\<^sub>b n t t')"
fixes prevAccepted :: "Term \<Rightarrow> Node set \<Rightarrow> Term set"
defines "prevAccepted t ns \<equiv> {t'. \<exists> n \<in> ns. promised\<^sub>b n t t'}"
(* invariants *)
assumes Q_intersects: "Q \<frown> Q"
assumes promised\<^sub>f: "\<lbrakk> promised\<^sub>f n t; t' < t \<rbrakk> \<Longrightarrow> \<not> accepted n t'"
assumes promised\<^sub>b_lt: "promised\<^sub>b n t t' \<Longrightarrow> t' < t"
assumes promised\<^sub>b_accepted: "promised\<^sub>b n t t' \<Longrightarrow> accepted n t'"
assumes promised\<^sub>b_max: "\<lbrakk> promised\<^sub>b n t t'; t' < t''; t'' < t \<rbrakk>
\<Longrightarrow> \<not> accepted n t''"
assumes proposed: "proposed t
\<Longrightarrow> \<exists> q \<in> Q. (\<forall> n \<in> q. promised n t)
\<and> (prevAccepted t q = {}
\<or> (\<exists> t'. v t = v t' \<and> maxTerm (prevAccepted t q) \<le> t' \<and> proposed t' \<and> t' < t))"
assumes proposed_finite: "finite {t. proposed t}"
assumes accepted: "accepted n t \<Longrightarrow> proposed t"
assumes committed: "committed t \<Longrightarrow> \<exists> q \<in> Q. \<forall> n \<in> q. accepted n t"
lemma (in oneSlot) prevAccepted_proposed: "prevAccepted t ns \<subseteq> {t. proposed t}"
using accepted prevAccepted_def promised\<^sub>b_accepted by fastforce
lemma (in oneSlot) prevAccepted_finite: "finite (prevAccepted p ns)"
using prevAccepted_proposed proposed_finite by (meson rev_finite_subset)
lemma (in oneSlot) Q_nonempty: "\<And>q. q \<in> Q \<Longrightarrow> q \<noteq> {}"
using Q_intersects by (auto simp add: intersects_def)
text \<open>The heart of the consistency proof is property P2b from \textit{Paxos made simple} by Lamport:\<close>
lemma (in oneSlot) p2b:
assumes "proposed t\<^sub>1" and "committed t\<^sub>2" and "t\<^sub>2 < t\<^sub>1"
shows "v t\<^sub>1 = v t\<^sub>2"
using assms
proof (induct t\<^sub>1 rule: less_induct)
case (less t\<^sub>1)
hence hyp: "\<And> t\<^sub>1'. \<lbrakk> t\<^sub>1' < t\<^sub>1; proposed t\<^sub>1'; t\<^sub>2 \<le> t\<^sub>1' \<rbrakk> \<Longrightarrow> v t\<^sub>1' = v t\<^sub>2"
using le_imp_less_or_eq by blast
from `proposed t\<^sub>1` obtain q\<^sub>1 t\<^sub>1' where
q\<^sub>1_quorum: "q\<^sub>1 \<in> Q" and
q\<^sub>1_promised: "\<And>n. n \<in> q\<^sub>1 \<Longrightarrow> promised n t\<^sub>1" and
q\<^sub>1_value: "prevAccepted t\<^sub>1 q\<^sub>1 = {} \<or> (v t\<^sub>1 = v t\<^sub>1' \<and> maxTerm (prevAccepted t\<^sub>1 q\<^sub>1) \<le> t\<^sub>1' \<and> proposed t\<^sub>1' \<and> t\<^sub>1' < t\<^sub>1)"
by (meson proposed)
from `committed t\<^sub>2` obtain q\<^sub>2 where
q\<^sub>2_quorum: "q\<^sub>2 \<in> Q" and
q\<^sub>2_accepted: "\<And>n. n \<in> q\<^sub>2 \<Longrightarrow> accepted n t\<^sub>2"
using committed by force
have "q\<^sub>1 \<inter> q\<^sub>2 \<noteq> {}"
using Q_intersects intersects_def less.prems q\<^sub>1_quorum q\<^sub>2_quorum by auto
then obtain n where n\<^sub>1: "n \<in> q\<^sub>1" and n\<^sub>2: "n \<in> q\<^sub>2" by auto
from n\<^sub>1 q\<^sub>1_promised have "promised n t\<^sub>1" by simp
moreover from n\<^sub>2 q\<^sub>2_accepted have "accepted n t\<^sub>2" by simp
ultimately obtain t\<^sub>2' where t\<^sub>2': "promised\<^sub>b n t\<^sub>1 t\<^sub>2'"
using less.prems(3) promised\<^sub>f promised_def by blast
have q\<^sub>1_value: "v t\<^sub>1 = v t\<^sub>1'" "maxTerm (prevAccepted t\<^sub>1 q\<^sub>1) \<le> t\<^sub>1'" "proposed t\<^sub>1'" "t\<^sub>1' < t\<^sub>1"
using n\<^sub>1 prevAccepted_def q\<^sub>1_value t\<^sub>2' by auto
note `v t\<^sub>1 = v t\<^sub>1'`
also have "v t\<^sub>1' = v t\<^sub>2"
proof (intro hyp)
have p: "maxTerm (prevAccepted t\<^sub>1 q\<^sub>1) \<in> prevAccepted t\<^sub>1 q\<^sub>1"
apply (intro maxTerm_mem prevAccepted_finite)
using n\<^sub>1 prevAccepted_def t\<^sub>2' by auto
show "t\<^sub>1' < t\<^sub>1" "proposed t\<^sub>1'" using q\<^sub>1_value by simp_all
have "t\<^sub>2 \<le> t\<^sub>2'"
by (meson \<open>accepted n t\<^sub>2\<close> less.prems(3) not_le promised\<^sub>b_max t\<^sub>2')
also have "t\<^sub>2' \<le> maxTerm (prevAccepted t\<^sub>1 q\<^sub>1)"
using n\<^sub>1 prevAccepted_def t\<^sub>2' prevAccepted_finite by (intro maxTerm_max, auto)
also have "... \<le> t\<^sub>1'" using q\<^sub>1_value by simp
finally show "t\<^sub>2 \<le> t\<^sub>1'" .
qed
finally show ?case .
qed
text \<open>From this, it follows that any two committed values are equal as desired.\<close>
lemma (in oneSlot) consistent:
assumes "committed t\<^sub>1" and "committed t\<^sub>2"
shows "v t\<^sub>1 = v t\<^sub>2"
using assms by (metis Q_nonempty accepted all_not_in_conv committed not_less_iff_gr_or_eq p2b)
text \<open>It will be useful later to know the conditions under which a value in a term can be committed,
which is spelled out here:\<close>
lemma (in oneSlot) commit:
assumes q_quorum: "q \<in> Q"
assumes q_accepted: "\<And>n. n \<in> q \<Longrightarrow> accepted n t\<^sub>0"
defines "committed' t \<equiv> committed t \<or> t = t\<^sub>0"
shows "oneSlot Q v promised\<^sub>f promised\<^sub>b proposed accepted committed'"
by (smt committed'_def Q_intersects oneSlot_axioms oneSlot_def q_accepted q_quorum)
end