krabka_verified/producer_snapshot/
snapshot_transaction_valid.rs1#[cfg(creusot)]
2use super::ProducerSnapshotEntryFacts;
3#[cfg(test)]
4use std::cell::Cell;
5use {super::ProducerReloadRange, creusot_std::prelude::*};
6
7#[cfg(creusot)]
9#[cfg_attr(test, mutants::skip)]
10#[logic(open)]
11pub fn kafka_reload_keeps(offset: Int, log_start: Int, log_end: Int) -> bool {
15 pearlite! { log_start < offset && offset <= log_end }
16}
17
18#[cfg(creusot)]
20#[cfg_attr(test, mutants::skip)]
21#[logic(open)]
22pub fn is_segment_base(bases: Seq<i64>, offset: Int) -> bool {
24 pearlite! { exists<j: Int> 0 <= j && j < bases.len() && bases[j]@ == offset }
25}
26
27#[cfg(creusot)]
29#[cfg_attr(test, mutants::skip)]
30#[logic(open)]
31pub fn kafka_stray_removed(snapshots: Seq<i64>, index: Int, bases: Seq<i64>) -> bool {
40 pearlite! {
41 !is_segment_base(bases, snapshots[index]@)
42 && !((forall<j: Int> 0 <= j && j < snapshots.len()
43 ==> snapshots[j]@ <= snapshots[index]@)
44 && (forall<j: Int> 0 <= j && j < bases.len()
45 ==> bases[j]@ < snapshots[index]@))
46 }
47}
48
49#[cfg(creusot)]
51#[cfg_attr(test, mutants::skip)]
52#[logic(open)]
53pub fn kafka_replay_start(range: ProducerReloadRange, snapshot: Option<i64>) -> Int {
58 pearlite! {
59 match snapshot {
60 Some(offset) => if offset@ >= range.local_start@ { offset@ } else { range.local_start@ },
61 None => if range.log_start@ >= range.local_start@ {
62 range.log_start@
63 } else {
64 range.local_start@
65 },
66 }
67 }
68}
69
70#[ensures(result == if remote_storage_enable {
79 match established {
80 Some(start) => start,
81 None => 0i64,
82 }
83} else {
84 local_log_start
85})]
86#[must_use]
87pub const fn producer_snapshot_reload_log_start(
88 remote_storage_enable: bool,
89 established: Option<i64>,
90 local_log_start: i64,
91) -> i64 {
92 if remote_storage_enable {
93 match established {
94 Some(start) => start,
95 None => 0,
96 }
97 } else {
98 local_log_start
99 }
100}
101
102#[ensures(result == kafka_reload_keeps(offset@, range.log_start@, range.log_end@))]
105#[must_use]
106pub const fn producer_snapshot_reload_keeps(offset: i64, range: ProducerReloadRange) -> bool {
107 range.log_start < offset && offset <= range.log_end
108}
109
110#[requires(index@ < [email protected]())]
117#[ensures(result == kafka_stray_removed(snapshots@, index@, segment_bases@))]
118#[must_use]
119pub fn producer_snapshot_stray(snapshots: &[i64], index: usize, segment_bases: &[i64]) -> bool {
120 let Some(&offset) = snapshots.get(index) else {
121 return false;
122 };
123 let mut is_base = false;
124 let mut above_every_base = true;
125 let mut i = 0usize;
126 #[invariant(i@ <= [email protected]())]
127 #[invariant(is_base == exists<j: Int> 0 <= j && j < i@ && segment_bases@[j]@ == offset@)]
128 #[invariant(above_every_base == forall<j: Int> 0 <= j && j < i@
129 ==> segment_bases@[j]@ < offset@)]
130 #[variant([email protected]() - i@)]
131 while i < segment_bases.len() {
132 if let Some(&base) = segment_bases.get(i) {
133 if base == offset {
134 is_base = true;
135 }
136 if base >= offset {
137 above_every_base = false;
138 }
139 }
140 i += 1;
141 }
142 let mut newest = true;
143 let mut k = 0usize;
144 #[invariant(k@ <= [email protected]())]
145 #[invariant(newest == forall<j: Int> 0 <= j && j < k@ ==> snapshots@[j]@ <= offset@)]
146 #[variant([email protected]() - k@)]
147 while k < snapshots.len() {
148 if let Some(&snapshot) = snapshots.get(k)
149 && snapshot > offset
150 {
151 newest = false;
152 }
153 k += 1;
154 }
155 !(is_base || (newest && above_every_base))
156}
157
158#[cfg_attr(
171 creusot,
172 requires(forall<i: Int, j: Int> 0 <= i && i < j && j < [email protected]()
173 ==> snapshots@[i]@ <= snapshots@[j]@)
174)]
175#[cfg_attr(
176 creusot,
177 requires(forall<i: Int, j: Int> 0 <= i && i < j && j < [email protected]()
178 ==> segment_bases@[i]@ <= segment_bases@[j]@)
179)]
180#[ensures([email protected]() == [email protected]())]
181#[ensures(forall<i: Int> 0 <= i && i < [email protected]()
182 ==> result@[i] == kafka_stray_removed(snapshots@, i, segment_bases@))]
183#[must_use]
184pub fn producer_snapshot_strays(snapshots: &[i64], segment_bases: &[i64]) -> Vec<bool> {
185 let mut removed = Vec::with_capacity(snapshots.len());
186 let greatest_base = segment_bases.last().copied();
187 let mut base_index = 0usize;
188 let mut snapshot_index = 0usize;
189 #[invariant(snapshot_index@ <= [email protected]())]
190 #[invariant(base_index@ <= [email protected]())]
191 #[invariant([email protected]() == snapshot_index@)]
192 #[invariant(snapshot_index@ < [email protected]() ==>
193 forall<i: Int> 0 <= i && i < base_index@
194 ==> segment_bases@[i]@ < snapshots@[snapshot_index@]@)]
195 #[invariant(forall<i: Int> 0 <= i && i < snapshot_index@
196 ==> removed@[i] == kafka_stray_removed(snapshots@, i, segment_bases@))]
197 #[variant([email protected]() - snapshot_index@)]
198 while snapshot_index < snapshots.len() {
199 record_classifier_visit();
200 let Some(&offset) = snapshots.get(snapshot_index) else {
201 break;
202 };
203 #[invariant(base_index@ <= [email protected]())]
204 #[invariant([email protected]() == snapshot_index@)]
205 #[invariant(forall<i: Int> 0 <= i && i < snapshot_index@
206 ==> removed@[i] == kafka_stray_removed(snapshots@, i, segment_bases@))]
207 #[invariant(forall<i: Int> 0 <= i && i < base_index@
208 ==> segment_bases@[i]@ < offset@)]
209 #[variant([email protected]() - base_index@)]
210 while base_index < segment_bases.len()
211 && matches!(segment_bases.get(base_index), Some(&base) if base < offset)
212 {
213 record_classifier_visit();
214 base_index += 1;
215 }
216
217 proof_assert!(base_index@ < [email protected]() ==>
218 forall<j: Int> base_index@ <= j && j < [email protected]()
219 ==> segment_bases@[base_index@]@ <= segment_bases@[j]@);
220 let is_base = base_index < segment_bases.len()
221 && matches!(segment_bases.get(base_index), Some(&base) if base == offset);
222 proof_assert!(is_base == is_segment_base(segment_bases@, offset@));
223 let newest = matches!(snapshots.last(), Some(&last) if last == offset);
224 proof_assert!(forall<j: Int> 0 <= j && j < [email protected]()
225 ==> snapshots@[j]@ <= snapshots@[[email protected]() - 1]@);
226 proof_assert!(newest == (forall<j: Int> 0 <= j && j < [email protected]()
227 ==> snapshots@[j]@ <= offset@));
228 let above_every_base = !matches!(greatest_base, Some(base) if offset <= base);
229 proof_assert!(above_every_base == (forall<j: Int> 0 <= j && j < [email protected]()
230 ==> segment_bases@[j]@ < offset@));
231 removed.push(!(is_base || (newest && above_every_base)));
232 proof_assert!(snapshot_index@ + 1 < [email protected]()
233 ==> offset@ <= snapshots@[snapshot_index@ + 1]@);
234 snapshot_index += 1;
235 }
236 removed
237}
238
239#[cfg(test)]
240thread_local! {
241 static CLASSIFIER_VISITS: Cell<usize> = const { Cell::new(0) };
242}
243
244fn record_classifier_visit() {
246 #[cfg(test)]
247 CLASSIFIER_VISITS.with(|visits| visits.set(visits.get() + 1));
248}
249
250#[cfg(test)]
251pub fn take_classifier_visits() -> usize { CLASSIFIER_VISITS.with(|visits| visits.replace(0)) }
252
253#[ensures(result == None ==>
259 forall<i: Int> 0 <= i && i < [email protected]()
260 ==> !kafka_reload_keeps(offsets@[i]@, range.log_start@, range.log_end@))]
261#[ensures(forall<selected: usize> result == Some(selected) ==>
262 selected@ < [email protected]()
263 && kafka_reload_keeps(offsets@[selected@]@, range.log_start@, range.log_end@)
264 && (forall<i: Int> 0 <= i && i < [email protected]()
265 && kafka_reload_keeps(offsets@[i]@, range.log_start@, range.log_end@)
266 ==> offsets@[i]@ <= offsets@[selected@]@))]
267#[must_use]
268pub fn producer_snapshot_latest_index(
269 offsets: &[i64],
270 range: ProducerReloadRange,
271) -> Option<usize> {
272 let mut best: Option<usize> = None;
273 let mut i = 0usize;
274 #[invariant(i@ <= [email protected]())]
275 #[invariant(match best {
276 None => forall<j: Int> 0 <= j && j < i@
277 ==> !kafka_reload_keeps(offsets@[j]@, range.log_start@, range.log_end@),
278 Some(selected) => selected@ < i@
279 && kafka_reload_keeps(offsets@[selected@]@, range.log_start@, range.log_end@)
280 && (forall<j: Int> 0 <= j && j < i@
281 && kafka_reload_keeps(offsets@[j]@, range.log_start@, range.log_end@)
282 ==> offsets@[j]@ <= offsets@[selected@]@),
283 })]
284 #[variant([email protected]() - i@)]
285 while i < offsets.len() {
286 if let Some(&offset) = offsets.get(i)
287 && producer_snapshot_reload_keeps(offset, range)
288 {
289 match best {
290 None => best = Some(i),
291 Some(selected) if matches!(offsets.get(selected), Some(¤t) if offset > current) =>
292 {
293 best = Some(i);
294 }
295 Some(_) => {}
296 }
297 }
298 i += 1;
299 }
300 best
301}
302
303#[cfg(creusot)]
305#[cfg_attr(test, mutants::skip)]
306#[logic(open)]
307pub fn snapshot_last_record_valid(snapshot_offset: Int, entry: ProducerSnapshotEntryFacts) -> bool {
312 pearlite! {
313 (entry.last_offset@ == -1 && entry.last_sequence@ == -1 && entry.offset_delta@ == 0)
314 || (entry.last_offset@ >= 0
315 && entry.last_sequence@ >= 0
316 && entry.offset_delta@ >= 0
317 && entry.last_offset@ >= entry.offset_delta@
318 && entry.last_offset@ < snapshot_offset)
319 }
320}
321
322#[cfg(creusot)]
324#[cfg_attr(test, mutants::skip)]
325#[logic(open)]
326pub fn snapshot_transaction_valid(snapshot_offset: Int, entry: ProducerSnapshotEntryFacts) -> bool {
329 pearlite! {
330 entry.current_txn_first_offset@ == -1
331 || (entry.current_txn_first_offset@ >= 0
332 && entry.current_txn_first_offset@ < snapshot_offset
333 && entry.current_txn_first_offset@ <= entry.last_offset@)
334 }
335}