krabka_verified/reconfiguration/
voter_reconfiguration_decision.rs1use super::{
2 CurrentVoterSet, ReconfigurationLeadership, TargetMembership, TargetVoter, VoterChangeKind,
3 VoterChangeRequest, VoterReconfigurationDecision, VoterReconfigurationPlan,
4};
5#[cfg(creusot)]
6use super::{
7 admitted_plan, is_admit, may_reconfigure, update_key_matches, voter_reconfiguration_rejection,
8 voter_set_removes,
9};
10#[cfg(creusot)]
11use creusot_std::prelude::*;
12
13#[cfg(creusot)]
14#[cfg_attr(test, mutants::skip)]
16#[logic(open)]
17fn voter_set_change_admission_valid(
18 voters: CurrentVoterSet,
19 kind: VoterChangeKind,
20 decision: VoterReconfigurationDecision,
21) -> bool {
22 pearlite! {
23 match decision {
24 VoterReconfigurationDecision::Admit(plan) =>
25 voters.kraft_version@ == 1 && admitted_plan(voters, kind, plan),
26 VoterReconfigurationDecision::UnsupportedKraftVersion => voters.kraft_version@ != 1,
27 _ => true,
28 }
29 }
30}
31
32#[cfg_attr(creusot, ensures(match voter_reconfiguration_rejection(leadership, voters, request, target) {
39 Some(rejection) => result == rejection,
40 None => match result {
41 VoterReconfigurationDecision::Admit(plan) => admitted_plan(voters, request.kind, plan),
42 _ => false,
43 },
44}))]
45#[cfg_attr(creusot, ensures(is_admit(result) ==> may_reconfigure(leadership, voters)))]
46#[cfg_attr(creusot, ensures(forall<plan: VoterReconfigurationPlan>
47 result == VoterReconfigurationDecision::Admit(plan)
48 ==> plan.next_voter_count@ > 0 && plan.next_kraft_version@ <= 1))]
49#[must_use]
50pub fn voter_reconfiguration_decision(
51 leadership: ReconfigurationLeadership,
52 voters: CurrentVoterSet,
53 request: VoterChangeRequest,
54 target: TargetVoter,
55) -> VoterReconfigurationDecision {
56 if !leadership.is_leader {
57 return VoterReconfigurationDecision::NotLeader;
58 }
59 if !leadership.no_pending_change {
60 return VoterReconfigurationDecision::InProgress;
61 }
62 if !leadership.epoch_committed {
63 return VoterReconfigurationDecision::EpochUncommitted;
64 }
65 if !voters.latest_controls_committed {
66 return VoterReconfigurationDecision::InProgress;
67 }
68 if voters.voter_count == 0 {
69 return VoterReconfigurationDecision::EmptyCurrentVoterSet;
70 }
71 if voters.kraft_version > 1 {
72 return VoterReconfigurationDecision::InvalidVersionTransition;
73 }
74 match request.kind {
75 VoterChangeKind::Add => add_decision(voters, target),
76 VoterChangeKind::Remove => remove_decision(voters, target),
77 VoterChangeKind::Update => update_decision(voters, target),
78 VoterChangeKind::FinalizeKraftVersion => finalize_decision(voters, request),
79 }
80}
81
82#[cfg_attr(creusot, ensures(match result {
84 VoterReconfigurationDecision::Admit(plan) =>
85 plan.next_voter_count@ == next_voter_count@
86 && plan.next_kraft_version@ == next_kraft_version@
87 && plan.write_voters && !plan.write_kraft_version && !plan.preflight_only,
88 _ => false,
89}))]
90fn admit_voter_set_change(
91 next_voter_count: usize,
92 next_kraft_version: u16,
93) -> VoterReconfigurationDecision {
94 VoterReconfigurationDecision::Admit(VoterReconfigurationPlan {
95 next_voter_count,
96 next_kraft_version,
97 write_voters: true,
98 write_kraft_version: false,
99 preflight_only: false,
100 })
101}
102
103#[cfg_attr(creusot, requires(decision_voter_set_valid(voters)))]
105#[cfg_attr(
106 creusot,
107 ensures(voter_set_change_admission_valid(voters, VoterChangeKind::Add, result,))
108)]
109#[cfg_attr(creusot, ensures(match result {
110 VoterReconfigurationDecision::Admit(_) =>
111 target.membership == TargetMembership::Absent
112 && target.version_compatible
113 && target.caught_up
114 && voters.voter_count@ < usize::MAX@,
115 VoterReconfigurationDecision::UnsupportedKraftVersion => true,
116 VoterReconfigurationDecision::DuplicateVoter => voters.kraft_version@ == 1
117 && target.membership != TargetMembership::Absent,
118 VoterReconfigurationDecision::IncompatibleVoter => voters.kraft_version@ == 1
119 && target.membership == TargetMembership::Absent
120 && !target.version_compatible,
121 VoterReconfigurationDecision::VoterNotCaughtUp => voters.kraft_version@ == 1
122 && target.membership == TargetMembership::Absent
123 && target.version_compatible
124 && !target.caught_up,
125 VoterReconfigurationDecision::InvalidVersionTransition => voters.kraft_version@ == 1
126 && target.membership == TargetMembership::Absent
127 && target.version_compatible
128 && target.caught_up
129 && voters.voter_count@ == usize::MAX@,
130 _ => false,
131}))]
132fn add_decision(voters: CurrentVoterSet, target: TargetVoter) -> VoterReconfigurationDecision {
133 if voters.kraft_version != 1 {
134 return VoterReconfigurationDecision::UnsupportedKraftVersion;
135 }
136 if !matches!(target.membership, TargetMembership::Absent) {
137 return VoterReconfigurationDecision::DuplicateVoter;
138 }
139 if !target.version_compatible {
140 return VoterReconfigurationDecision::IncompatibleVoter;
141 }
142 if !target.caught_up {
143 return VoterReconfigurationDecision::VoterNotCaughtUp;
144 }
145 let Some(next_voter_count) = voters.voter_count.checked_add(1) else {
147 return VoterReconfigurationDecision::InvalidVersionTransition;
148 };
149 admit_voter_set_change(next_voter_count, voters.kraft_version)
150}
151
152#[cfg_attr(creusot, requires(decision_voter_set_valid(voters)))]
156#[cfg_attr(
157 creusot,
158 ensures(voter_set_change_admission_valid(voters, VoterChangeKind::Remove, result,))
159)]
160#[cfg_attr(creusot, ensures(match result {
161 VoterReconfigurationDecision::Admit(_) =>
162 voter_set_removes(target.membership, voters),
163 VoterReconfigurationDecision::UnsupportedKraftVersion => true,
164 VoterReconfigurationDecision::VoterNotFound => voters.kraft_version@ == 1
165 && !voter_set_removes(target.membership, voters),
166 _ => false,
167}))]
168fn remove_decision(voters: CurrentVoterSet, target: TargetVoter) -> VoterReconfigurationDecision {
169 if voters.kraft_version != 1 {
170 return VoterReconfigurationDecision::UnsupportedKraftVersion;
171 }
172 if !matches!(target.membership, TargetMembership::PresentSameDirectory)
173 || voters.voter_count == 1
174 {
175 return VoterReconfigurationDecision::VoterNotFound;
176 }
177 admit_voter_set_change(voters.voter_count - 1, voters.kraft_version)
178}
179
180#[cfg_attr(creusot, requires(decision_voter_set_valid(voters)))]
182#[cfg_attr(creusot, ensures(match result {
183 VoterReconfigurationDecision::Admit(plan) =>
184 target.version_compatible
185 && update_key_matches(target.membership)
186 && admitted_plan(voters, VoterChangeKind::Update, plan),
187 VoterReconfigurationDecision::IncompatibleVoter => !target.version_compatible,
188 VoterReconfigurationDecision::VoterNotFound => target.version_compatible
189 && target.membership == TargetMembership::Absent,
190 VoterReconfigurationDecision::DirectoryMismatch => target.version_compatible
191 && target.membership != TargetMembership::Absent
192 && !update_key_matches(target.membership),
193 _ => false,
194}))]
195fn update_decision(voters: CurrentVoterSet, target: TargetVoter) -> VoterReconfigurationDecision {
196 if !target.version_compatible {
197 return VoterReconfigurationDecision::IncompatibleVoter;
198 }
199 match target.membership {
200 TargetMembership::Absent => return VoterReconfigurationDecision::VoterNotFound,
201 TargetMembership::PresentOtherDirectory => {
202 return VoterReconfigurationDecision::DirectoryMismatch;
203 }
204 TargetMembership::PresentUnknownDirectory | TargetMembership::PresentSameDirectory => {}
205 }
206 let preflight_only = voters.kraft_version == 0;
207 VoterReconfigurationDecision::Admit(VoterReconfigurationPlan {
208 next_voter_count: voters.voter_count,
209 next_kraft_version: voters.kraft_version,
210 write_voters: !preflight_only,
211 write_kraft_version: false,
212 preflight_only,
213 })
214}
215
216#[cfg_attr(creusot, requires(voters.voter_count@ > 0))]
219#[cfg_attr(creusot, ensures(match result {
220 VoterReconfigurationDecision::Admit(plan) =>
221 voters.kraft_version@ == 0
222 && request.requested_kraft_version@ == 1
223 && voters.all_voters_support_requested
224 && admitted_plan(voters, VoterChangeKind::FinalizeKraftVersion, plan),
225 VoterReconfigurationDecision::InvalidVersionTransition => voters.kraft_version@ != 0
226 || request.requested_kraft_version@ != 1,
227 VoterReconfigurationDecision::IncompatibleVoter => voters.kraft_version@ == 0
228 && request.requested_kraft_version@ == 1
229 && !voters.all_voters_support_requested,
230 _ => false,
231}))]
232fn finalize_decision(
233 voters: CurrentVoterSet,
234 request: VoterChangeRequest,
235) -> VoterReconfigurationDecision {
236 if voters.kraft_version != 0 || request.requested_kraft_version != 1 {
237 return VoterReconfigurationDecision::InvalidVersionTransition;
238 }
239 if !voters.all_voters_support_requested {
240 return VoterReconfigurationDecision::IncompatibleVoter;
241 }
242 VoterReconfigurationDecision::Admit(VoterReconfigurationPlan {
243 next_voter_count: voters.voter_count,
244 next_kraft_version: 1,
245 write_voters: true,
246 write_kraft_version: true,
247 preflight_only: false,
248 })
249}
250
251#[cfg(creusot)]
253#[cfg_attr(test, mutants::skip)]
254#[logic(open)]
255fn decision_voter_set_valid(voters: CurrentVoterSet) -> bool {
257 pearlite! { voters.voter_count@ > 0 && voters.kraft_version@ <= 1 }
258}