Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: impl on recv RequestVote #28

Merged
merged 16 commits into from
Jan 18, 2025
18 changes: 9 additions & 9 deletions compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@
- [x] term: currentTerm, for candidate to update itself
- [x] voteGranted: true means candidate received vote
#### Receiver implementation
- [ ] Reply false if term < currentTerm (§5.1)
- [ ] If candidate’s log is at least as up-to-date as receiver’s log
- [ ] and votedFor is null, grant vote (§5.2, §5.4)
- [ ] and votedFor is candidateId, grant vote (§5.2, §5.4)
- [x] Reply false if term < currentTerm (§5.1)
- [x] If candidate’s log is at least as up-to-date as receiver’s log
- [x] and votedFor is null, grant vote (§5.2, §5.4)
- [x] and votedFor is candidateId, grant vote (§5.2, §5.4)

## 5: The Raft consensus algo
- [ ] first elect a leader
Expand Down Expand Up @@ -199,12 +199,12 @@
- [ ] a candidate cant win an election unless its log contains all committed entries.
- [ ] a candidate must get a vote from a majority of servers
- `up-to-date`: a log is considered more up-to-date than another log if:
- [ ] compare the index and term of the last entry of A's and B's log
- [ ] if the entries have different term: the higher term is more up-to-date
- [ ] if the term is the same: the longer log (higher index) is more up-to-date
- [x] compare the index and term of the last entry of A's and B's log
- [x] if the entries have different term: the higher term is more up-to-date
- [x] if the term is the same: the longer log (higher index) is more up-to-date
- The RequestVote RPC helps ensure the leader's log is `up-to-date`
- [ ] RequestVote includes info about candidate's log
- [ ] voter denies vote if its own log is more `up-to-date`
- [x] RequestVote includes info about candidate's log
- [x] voter denies vote if its own log is more `up-to-date`

#### 5.4.2 Committing entries from previous terms
- [ ] a leader knows an entry from its **current term** (not true for previous terms) is committed, once its stored (replicated) on a majority of servers
Expand Down
88 changes: 80 additions & 8 deletions src/log.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
mod entry;
mod idx;
mod term;
mod term_idx;

pub use entry::Entry;
pub use idx::Idx;
use std::cmp::Ordering;
pub use term::Term;
pub use term_idx::TermIdx;

mod entry;
mod idx;
mod term;
mod term_idx;

#[derive(Debug)]
pub struct Log {
pub entries: Vec<Entry>,
Expand All @@ -25,15 +26,21 @@ impl Log {
}

pub(super) fn prev_idx(&self) -> Idx {
Idx::from(self.entries.len() as u64)
if self.entries.is_empty() {
Idx::initial()
} else {
Idx::from(self.entries.len() as u64)
}
}

pub fn next_idx(&self) -> Idx {
self.prev_idx() + 1
}

pub fn last_term(&self) -> Term {
self.entries.last().map_or(Term::initial(), |e| e.term)
pub fn last_term_idx(&self) -> TermIdx {
let last_term = self.entries.last().map_or(Term::initial(), |e| e.term);
let last_idx = self.prev_idx();
TermIdx::builder().with_term(last_term).with_idx(last_idx)
}

pub fn term_at_idx(&self, idx: &Idx) -> Option<Term> {
Expand Down Expand Up @@ -103,6 +110,26 @@ impl Log {
}
self.entries.get(idx.as_log_idx())
}

pub fn is_candidate_log_up_to_date(&mut self, rpc_term_idx: &TermIdx) -> bool {
//% Compliance:
//% `up-to-date`: a log is considered more up-to-date than another log if:
//% - compare the index and term of the last entry of A's and B's log
let log_term_idx = self.last_term_idx();
let term_cmp = rpc_term_idx.term.cmp(&log_term_idx.term);
let idx_cmp = rpc_term_idx.idx.cmp(&log_term_idx.idx);
match (term_cmp, idx_cmp) {
//% Compliance:
//% - if the entries have different term: the higher term is more up-to-date
(Ordering::Greater, _) => true,
(Ordering::Less, _) => false,
//% Compliance:
//% - if the term is the same: the longer log (higher index) is more up-to-date
(Ordering::Equal, Ordering::Less) => false,
(Ordering::Equal, Ordering::Equal) => true,
(Ordering::Equal, Ordering::Greater) => true,
}
}
}

#[must_use]
Expand Down Expand Up @@ -275,4 +302,49 @@ mod tests {
);
assert!(matches!(doesnt_exist_outcome, MatchOutcome::DoesntExist));
}

#[test]
fn test_log_up_to_date() {
let mut log = Log::new();

let t1 = Term::from(1);
let t2 = Term::from(2);
let t3 = Term::from(3);
let i1 = Idx::from(1);
let i2 = Idx::from(2);
let i3 = Idx::from(3);
let ti_initial = TermIdx::initial();

// Initial IS up-to-date
// log: []
assert!(log.is_candidate_log_up_to_date(&ti_initial));

// log: [ [t:1] [t:2] ]
log.push(vec![Entry::new(t1, 8)]);
log.push(vec![Entry::new(t2, 8)]);
assert_eq!(log.entries.len(), 2);

// Initial NOT up-to-date
assert!(!log.is_candidate_log_up_to_date(&ti_initial));

// == Equal TermIdx ==
let term_idx_eq = TermIdx::builder().with_term(t2).with_idx(i2);
assert!(log.is_candidate_log_up_to_date(&term_idx_eq));

// == Different Term ==
// term <
let term_lt = TermIdx::builder().with_term(t1).with_idx(i2);
assert!(!log.is_candidate_log_up_to_date(&term_lt));
// term >
let term_gt = TermIdx::builder().with_term(t3).with_idx(i2);
assert!(log.is_candidate_log_up_to_date(&term_gt));

// == Same Term ==
// idx <
let idx_lt = TermIdx::builder().with_term(t2).with_idx(i1);
assert!(!log.is_candidate_log_up_to_date(&idx_lt));
// idx >
let idx_gt = TermIdx::builder().with_term(t2).with_idx(i3);
assert!(log.is_candidate_log_up_to_date(&idx_gt));
}
}
2 changes: 2 additions & 0 deletions src/log/idx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ impl AddAssign<u64> for Idx {

impl From<u64> for Idx {
fn from(value: u64) -> Self {
// Idx values should be greater than 0
debug_assert!(value > 0);
Idx(value)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ mod tests {
assert!(matches!(mode, Mode::F(_)));

// decode the sent RPC
let leader_io = &mut peer_map.get_mut(&leader_id).unwrap().io;
let leader_io = &mut context.peer_map.get_mut(&leader_id).unwrap().io;
let sent_request_vote = helper_inspect_sent_rpc(leader_io);
assert!(leader_io.send_queue.is_empty());

Expand Down Expand Up @@ -274,11 +274,11 @@ mod tests {
);
mode.on_recv(append_entries, &mut context);

// expect Mode::Follower
// expect Mode::Candidate
assert!(matches!(mode, Mode::C(_)));

// decode the sent RPC
let peer_io = &mut peer_map.get_mut(&peer_id).unwrap().io;
let peer_io = &mut context.peer_map.get_mut(&peer_id).unwrap().io;
assert!(peer_io.send_queue.len() == 1);
let sent_request_vote = helper_inspect_sent_rpc(peer_io);

Expand Down
Loading
Loading