Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/apollo_consensus/src/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ pub(crate) enum StateMachineEvent {
Prevote(Vote),
/// Precommit message, sent from the SHC to the state machine.
Precommit(Vote),
/// TimeoutPropose event, sent from the state machine to the SHC.
/// TimeoutPropose event, sent from the SHC to the state machine.
TimeoutPropose(Round),
/// TimeoutPrevote event, sent from the state machine to the SHC.
/// TimeoutPrevote event, sent from the SHC to the state machine.
TimeoutPrevote(Round),
/// TimeoutPrecommit event, sent from the state machine to the SHC.
/// TimeoutPrecommit event, sent from the SHC to the state machine.
TimeoutPrecommit(Round),
/// Used by the SHC to rebroadcast a self-vote.
RebroadcastVote(Vote),
Expand Down
34 changes: 34 additions & 0 deletions crates/apollo_consensus/src/state_machine_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,3 +610,37 @@ fn number_of_required_votes(quorum_type: QuorumType) {
);
assert!(wrapper.next_request().is_none());
}

#[test]
fn observer_does_not_record_self_votes() {
// Set up as an observer.
let id = *VALIDATOR_ID;
let mut wrapper = TestWrapper::new(id, 4, |_: Round| *PROPOSER_ID, true, QuorumType::Byzantine);

// Start and receive proposal validation completion.
wrapper.start();
assert_eq!(wrapper.next_request().unwrap(), SMRequest::ScheduleTimeoutPropose(ROUND));
assert!(wrapper.next_request().is_none());
wrapper.send_finished_validation(PROPOSAL_ID, ROUND);

// Reach mixed prevote quorum with peer votes only (self not counted).
wrapper.send_prevote(PROPOSAL_ID, ROUND);
wrapper.send_prevote(PROPOSAL_ID, ROUND);
// No quorum yet, we didn't vote.
assert!(wrapper.next_request().is_none());
wrapper.send_prevote(PROPOSAL_ID, ROUND);
assert_eq!(wrapper.next_request().unwrap(), SMRequest::ScheduleTimeoutPrevote(ROUND));

// Timeout prevote triggers self precommit(nil) path, which observers must not record/broadcast.
wrapper.send_timeout_prevote(ROUND);
assert!(wrapper.next_request().is_none());
assert_eq!(wrapper.state_machine.last_self_precommit(), None);

// Reach mixed precommit quorum with peer votes only and ensure timeout is scheduled.
wrapper.send_precommit(PROPOSAL_ID, ROUND);
wrapper.send_precommit(PROPOSAL_ID, ROUND);
// No quorum yet, we didn't vote.
assert!(wrapper.next_request().is_none());
wrapper.send_precommit(PROPOSAL_ID, ROUND);
assert_eq!(wrapper.next_request().unwrap(), SMRequest::ScheduleTimeoutPrecommit(ROUND));
}
Loading