viewstamped-replication 0.6.0

A Rust-based implementation of the Viewstamped Replication consensus protocol.
Documentation
use crate::identifiers::ReplicaIdentifier;
use crate::stamps::View;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

#[derive(Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq)]
pub enum HealthStatus {
    #[default]
    Normal,
    Suspect,
    Unhealthy,
}

pub trait HealthDetector {
    fn detect(&mut self, view: View, replica: ReplicaIdentifier) -> HealthStatus;
}

impl HealthDetector for HealthStatus {
    fn detect(&mut self, _: View, _: ReplicaIdentifier) -> HealthStatus {
        *self
    }
}

#[derive(Debug, Default)]
pub struct Suspect;

impl HealthDetector for Suspect {
    fn detect(&mut self, _: View, _: ReplicaIdentifier) -> HealthStatus {
        HealthStatus::Suspect
    }
}

#[derive(Debug, Default)]
pub struct Unhealthy;

impl HealthDetector for Unhealthy {
    fn detect(&mut self, _: View, _: ReplicaIdentifier) -> HealthStatus {
        HealthStatus::Unhealthy
    }
}

#[derive(Clone, Debug, Default)]
pub struct LocalHealthDetector {
    status: Rc<RefCell<HashMap<ReplicaIdentifier, HealthStatus>>>,
}

impl LocalHealthDetector {
    pub fn set_status(&mut self, replica: ReplicaIdentifier, status: HealthStatus) {
        self.status.borrow_mut().insert(replica, status);
    }
}

impl HealthDetector for LocalHealthDetector {
    fn detect(&mut self, view: View, replica: ReplicaIdentifier) -> HealthStatus {
        self.status
            .borrow()
            .get(&replica.primary(view))
            .copied()
            .unwrap_or(HealthStatus::Normal)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::identifiers::GroupIdentifier;

    #[test]
    fn order() {
        assert!(HealthStatus::Normal < HealthStatus::Suspect);
        assert!(HealthStatus::Suspect < HealthStatus::Unhealthy);
        assert!(HealthStatus::Normal < HealthStatus::Unhealthy);
    }

    #[test]
    fn local() {
        let group = GroupIdentifier::new(3);
        let view = View::default();
        let replica = group.replicas(view).next().unwrap();
        let mut detector = LocalHealthDetector::default();
        let mut clone = detector.clone();

        clone.set_status(group.primary(view), HealthStatus::Unhealthy);

        assert_eq!(detector.detect(view, replica), HealthStatus::Unhealthy);
        assert_eq!(detector.detect(view.next(), replica), HealthStatus::Normal);
    }
}