Skip to main content

krabka_verified/consensus/
watermark.rs

1#[cfg(creusot)]
2use super::{count_ge, count_ge_prefix, hwm_member_at, lemma_hwm_member_maximal};
3use creusot_std::prelude::*;
4
5// cargo-mutants: #[cfg(creusot)] spec function; not compiled outside Creusot, so no test can tell.
6#[cfg(creusot)]
7#[cfg_attr(test, mutants::skip)]
8#[logic(open)]
9/// The current watermark and follower acknowledgements fit within the log.
10fn watermark_inputs_bounded(current: Int, end: Int, followers: Seq<i64>) -> bool {
11    pearlite! {
12        current <= end && (forall<k: Int> 0 <= k && k < followers.len() ==> followers[k]@ <= end)
13    }
14}
15
16#[requires(1 <= majority@ && majority@ <= [email protected]() + 1)]
17#[ensures(result == (count_ge(log_end@, follower_offsets@, cand@, leader_counts) >= majority@))]
18pub(super) fn candidate_has_majority(
19    log_end: i64,
20    follower_offsets: &[i64],
21    cand: i64,
22    majority: usize,
23    leader_counts: bool,
24) -> bool {
25    let mut count: usize = 0;
26    if leader_counts && log_end >= cand && count < majority {
27        count += 1;
28    }
29
30    let n = follower_offsets.len();
31    let mut j = 0;
32    #[invariant(j@ <= n@)]
33    #[invariant({
34        let seen = count_ge_prefix(log_end@, follower_offsets@, cand@, j@ + 1, leader_counts);
35        count@ == if seen < majority@ { seen } else { majority@ }
36    })]
37    #[invariant(count@ <= majority@)]
38    #[variant(n@ - j@)]
39    while j < n {
40        if let Some(&x) = follower_offsets.get(j)
41            && x >= cand
42            && count < majority
43        {
44            count += 1;
45        }
46        j += 1;
47    }
48
49    count >= majority
50}
51
52/// The HWM as the majority-th largest match offset across the leader's own log
53/// end, when `leader_counts`, and every follower's acknowledged fetch offset.
54///
55/// The leader-completeness rule of Raft Fig.8 and KIP-595 gates this value: the
56/// HWM may only advance once the majority offset is strictly past
57/// `epoch_start_offset`. The HWM never regresses below `current_hwm`.
58///
59/// When `leader_counts` is `false` (a leader that its own `VotersRecord`
60/// removed), the followers alone must be able to reach `majority`, which is
61/// the second precondition.
62///
63/// The function computes the majority-th largest by its definition, and not by
64/// a sort. That definition is the greatest member m of
65/// `{log_end} U follower_offsets` with at least `majority` members >= m. Voter
66/// counts are tiny, at most about 7, and the Creusot proof quantifies over a
67/// loop that mirrors the definition.
68#[requires(1 <= majority@ && majority@ <= [email protected]() + 1)]
69#[requires(leader_counts || majority@ <= [email protected]())]
70#[requires(watermark_inputs_bounded(current_hwm@, log_end@, follower_offsets@))]
71#[ensures(result@ >= current_hwm@)]
72#[ensures(result@ <= log_end@)]
73#[ensures(forall<v: Int> v > epoch_start_offset@
74    && count_ge(log_end@, follower_offsets@, v, leader_counts) >= majority@
75    ==> v <= result@)]
76#[ensures(result@ > current_hwm@
77    ==> result@ > epoch_start_offset@
78        && count_ge(log_end@, follower_offsets@, result@, leader_counts) >= majority@)]
79#[must_use]
80pub fn recompute_high_watermark(
81    log_end: i64,
82    follower_offsets: &[i64],
83    majority: usize,
84    epoch_start_offset: i64,
85    current_hwm: i64,
86    leader_counts: bool,
87) -> i64 {
88    let n = follower_offsets.len();
89    let mut majority_offset =
90        if candidate_has_majority(log_end, follower_offsets, log_end, majority, leader_counts) {
91            log_end
92        } else {
93            i64::MIN
94        };
95
96    let mut i = 0;
97    #[invariant(i@ <= n@)]
98    #[invariant(majority_offset@ <= log_end@)]
99    #[invariant(majority_offset@ == -9223372036854775807 - 1
100        || count_ge(log_end@, follower_offsets@, majority_offset@, leader_counts) >= majority@)]
101    #[invariant(forall<k: Int> 0 <= k && k < i@ + 1
102        && count_ge(log_end@, follower_offsets@, hwm_member_at(log_end@, follower_offsets@, k), leader_counts) >= majority@
103        ==> hwm_member_at(log_end@, follower_offsets@, k) <= majority_offset@)]
104    #[variant(n@ - i@)]
105    while i < n {
106        if let Some(&cand) = follower_offsets.get(i)
107            && cand > majority_offset
108            && candidate_has_majority(log_end, follower_offsets, cand, majority, leader_counts)
109        {
110            majority_offset = cand;
111        }
112        i += 1;
113    }
114    #[cfg(creusot)]
115    lemma_hwm_member_maximal(
116        log_end,
117        follower_offsets,
118        majority,
119        majority_offset,
120        leader_counts,
121    );
122    let gated = if majority_offset > epoch_start_offset {
123        majority_offset
124    } else {
125        current_hwm
126    };
127    gated.max(current_hwm)
128}
129
130/// The watermark a majority has acknowledged, never below `current`.
131///
132/// This is [`recompute_high_watermark`] with the leader's log end always
133/// counted and no leader-epoch gate. The result is the greater of `current`
134/// and the majority frontier: the greatest member of
135/// `{log_end} U follower_offsets` that at least `majority` members reach.
136/// Passing `current` as both the gate and the floor is what makes it that: the
137/// frontier is taken only when it passes `current`, and `current` otherwise.
138///
139/// A caller that needs Raft's current-term rule, the gate on the leader's
140/// first record of its own epoch, uses [`recompute_high_watermark`] instead.
141#[requires(1 <= majority@ && majority@ <= [email protected]() + 1)]
142#[requires(watermark_inputs_bounded(current@, log_end@, follower_offsets@))]
143#[ensures(result@ >= current@)]
144#[ensures(result@ <= log_end@)]
145#[ensures(forall<v: Int> count_ge(log_end@, follower_offsets@, v, true) >= majority@
146    ==> v <= result@)]
147#[ensures(result@ > current@
148    ==> count_ge(log_end@, follower_offsets@, result@, true) >= majority@)]
149#[must_use]
150pub fn majority_watermark(
151    log_end: i64,
152    follower_offsets: &[i64],
153    majority: usize,
154    current: i64,
155) -> i64 {
156    recompute_high_watermark(log_end, follower_offsets, majority, current, current, true)
157}