1use creusot_std::prelude::*;
4
5#[cfg(creusot)]
7#[cfg_attr(test, mutants::skip)]
8#[logic(open)]
9pub(crate) fn first_unstable_frontier(starts: Seq<i64>, log_end: Int, lso: Int) -> bool {
11 pearlite! {
12 lso <= log_end
13 && (forall<i: Int> 0 <= i && i < starts.len() ==> starts[i]@ <= log_end)
14 && ((starts.len() == 0 && lso == log_end)
15 || (starts.len() > 0
16 && (exists<i: Int> 0 <= i && i < starts.len() && lso == starts[i]@)
17 && (forall<i: Int> 0 <= i && i < starts.len() ==> lso <= starts[i]@)))
18 }
19}
20
21#[krabka_macros::model_type]
24pub enum LogBatchKind {
25 Data,
26 Abort,
27 Commit,
28 Barrier,
29 OtherControl,
30}
31
32#[krabka_macros::model_type]
34pub enum TransactionPidInstallDecision {
35 RejectWrongPartition,
36 RejectCurrentIdentity,
37 RejectStagedIdentity,
38 RejectCollision,
39 Apply,
40}
41
42#[krabka_macros::model_type]
44pub enum TransactionMarkerMaterializationDecision {
45 RejectMalformed,
46 RejectProducerEpoch,
47 RejectCoordinatorEpoch,
48 Retry,
49 AppendWithoutOffsetPublication,
50 AppendAndPublishOffsets,
51}
52
53#[krabka_macros::model_type]
55pub struct TransactionMarkerRequest {
56 pub producer_id: i64,
57 pub producer_epoch: i16,
58 pub coordinator_epoch: i32,
60 pub is_commit: bool,
62 pub is_offsets_partition: bool,
64}
65
66#[krabka_macros::model_type]
68pub struct TransactionMarkerPartitionState {
69 pub producer_epoch: i16,
71 pub coordinator_epoch: i32,
73 pub has_pending_transaction: bool,
75}
76
77#[krabka_macros::model_type]
80pub enum TransactionReaperCompletionDecision {
81 RejectMalformed,
83 RejectStaleIdentity,
85 RejectChangedPreparedState,
88 AlreadyComplete,
90 Proceed,
92}
93
94#[krabka_macros::model_type]
104pub enum TransactionRegistrationDecision {
105 RejectNotCoordinator,
106 RejectUnknownProducer,
107 RejectPendingTransition,
108 RejectProducerId,
109 RejectProducerEpoch,
110 RejectState,
111 PersistRetry,
112 PersistRegistration,
113}
114
115#[krabka_macros::model_type]
117pub struct TransactionRegistrationFacts {
118 pub ownership: TransactionRegistrationOwnershipFacts,
119 pub identity: TransactionRegistrationIdentityFacts,
120 pub state: TransactionRegistrationStateFacts,
121}
122
123#[krabka_macros::model_type]
124pub struct TransactionRegistrationOwnershipFacts {
125 pub is_coordinator: bool,
126 pub producer_id_valid: bool,
127 pub entry_exists: bool,
128}
129
130#[krabka_macros::model_type]
131pub struct TransactionRegistrationIdentityFacts {
132 pub pending_transition: bool,
135 pub matching: TransactionRegistrationIdentityMatchFacts,
136}
137
138#[krabka_macros::model_type]
139pub struct TransactionRegistrationIdentityMatchFacts {
140 pub transactional_id_matches: bool,
141 pub producer_id_matches: bool,
142 pub producer_epoch_matches: bool,
143}
144
145#[krabka_macros::model_type]
146pub struct TransactionRegistrationStateFacts {
147 pub state_allows_registration: bool,
148 pub state_is_ongoing: bool,
152 pub exact_partitions_registered: bool,
153}
154
155#[krabka_macros::model_type]
157pub enum TransactionCompletionDecision {
158 Proceed,
159 AlreadyComplete,
160 RejectStaleIdentity,
161 RejectState,
162}
163
164pub const NO_TRANSACTION_TIMEOUT_MS: i32 = i32::MAX;
166
167#[krabka_macros::model_type]
169pub enum IdleTransactionState {
170 Ongoing,
171 Other,
172}
173
174#[krabka_macros::model_type]
176pub struct TransactionSnapshot {
177 pub pid: i64,
178 pub epoch: i16,
179 pub state: i8,
180}
181
182#[krabka_macros::model_type]
184pub struct TransactionIdentity {
185 pub pid: i64,
186 pub epoch: i16,
187}
188
189impl TransactionSnapshot {
190 #[ensures(result.pid == self.pid && result.epoch == self.epoch)]
192 #[must_use]
193 pub fn identity(self) -> TransactionIdentity {
194 TransactionIdentity {
195 pid: self.pid,
196 epoch: self.epoch,
197 }
198 }
199}
200
201#[krabka_macros::model_type]
203pub enum InitProducerIdIdentityDecision {
204 BumpWithoutIdentity,
208 Bump,
211 Retry,
215 Fenced,
217}
218
219pub const EXHAUSTED_PRODUCER_EPOCH: i16 = i16::MAX - 1;
225
226mod marker;
227#[cfg(creusot)]
228pub use marker::{
229 has_identity, is_identity, marker_completed_retry, marker_generation_current,
230 marker_publishes_offsets, marker_well_formed, snapshot_eq,
231};
232pub use marker::{
233 log_batch_kind, transaction_marker_equal_epoch_fenced,
234 transaction_marker_materialization_decision,
235};
236
237mod reaper;
238pub use reaper::{
239 transaction_partition_registration, transaction_pid_install_decision,
240 transaction_reaper_completion_decision,
241};
242
243mod next_producer_identity;
244pub use next_producer_identity::{
245 aborted_transaction_interval, aborted_transaction_overlaps, first_unstable_offset,
246 next_producer_identity, should_abort_idle_transaction, transaction_marker_closes,
247};
248
249mod abort_rows;
250pub use abort_rows::unique_aborted_transaction_rows;
251
252mod producer_identity;
253pub use producer_identity::{init_producer_id_identity_decision, transaction_completion_decision};
254
255#[cfg(creusot)]
257#[cfg_attr(test, mutants::skip)]
258#[logic(open)]
259pub(crate) fn unstable_fetch_limit_maximal(
261 starts: Seq<i64>,
262 log_end: Int,
263 high_watermark: Int,
264 deliverable: Int,
265 limit: Int,
266) -> bool {
267 pearlite! { forall<v: Int> v <= log_end && v <= high_watermark && v <= deliverable
268 && (forall<i: Int> 0 <= i && i < starts.len() ==> v <= starts[i]@) ==> v <= limit }
269}
270
271#[cfg(creusot)]
273#[cfg_attr(test, mutants::skip)]
274#[logic(open)]
275pub(crate) fn pending_starts_bound_fetch(
277 starts: Seq<i64>,
278 end: Int,
279 stable: Int,
280 limit: Int,
281) -> bool {
282 pearlite! { forall<i: Int> 0 <= i && i < starts.len()
283 ==> starts[i]@ <= end && stable <= starts[i]@ && limit <= starts[i]@ }
284}
285
286#[cfg(creusot)]
288#[cfg_attr(test, mutants::skip)]
289#[logic(open)]
290pub(crate) fn pending_starts_before_marker(starts: Seq<i64>, base: Int) -> bool {
292 pearlite! { forall<i: Int> 0 <= i && i < starts.len() ==> 0 <= starts[i]@ && starts[i]@ <= base }
293}
294
295#[cfg(test)]
296mod tests;