Skip to main content

aethex_consensus/
validator_set.rs

1use axiom_core::types::ValidatorId;
2use serde::{Deserialize, Serialize};
3
4/// A single validator with its current voting power.
5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
6pub struct Validator {
7    pub id: ValidatorId,
8    pub voting_power: u64,
9}
10
11/// The ordered, weighted validator set for one epoch.
12#[derive(Clone, Debug, Default, Serialize, Deserialize)]
13pub struct ValidatorSet {
14    validators: Vec<Validator>,
15    total_power: u64,
16}
17
18impl ValidatorSet {
19    pub fn new(mut validators: Vec<Validator>) -> Self {
20        validators.sort_by(|a, b| b.voting_power.cmp(&a.voting_power));
21        let total_power = validators.iter().map(|v| v.voting_power).sum();
22        ValidatorSet { validators, total_power }
23    }
24
25    pub fn is_empty(&self) -> bool {
26        self.validators.is_empty()
27    }
28
29    pub fn len(&self) -> usize {
30        self.validators.len()
31    }
32
33    pub fn total_power(&self) -> u64 {
34        self.total_power
35    }
36
37    pub fn get(&self, id: &ValidatorId) -> Option<&Validator> {
38        self.validators.iter().find(|v| &v.id == id)
39    }
40
41    pub fn contains(&self, id: &ValidatorId) -> bool {
42        self.get(id).is_some()
43    }
44
45    pub fn voting_power_of(&self, id: &ValidatorId) -> u64 {
46        self.get(id).map(|v| v.voting_power).unwrap_or(0)
47    }
48
49    /// Deterministic leader for (height, round): weighted round-robin.
50    pub fn proposer(&self, height: u64, round: u32) -> Option<&Validator> {
51        if self.validators.is_empty() {
52            return None;
53        }
54        let idx = ((height + round as u64) % self.validators.len() as u64) as usize;
55        Some(&self.validators[idx])
56    }
57
58    pub fn iter(&self) -> impl Iterator<Item = &Validator> {
59        self.validators.iter()
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use axiom_crypto::Keypair;
67
68    fn make_vset(n: usize) -> ValidatorSet {
69        let validators: Vec<Validator> = (0..n as u8)
70            .map(|i| Validator {
71                id: Keypair::from_bytes(&[i + 1; 32]).public_key(),
72                voting_power: 100,
73            })
74            .collect();
75        ValidatorSet::new(validators)
76    }
77
78    #[test]
79    fn total_power() {
80        let vs = make_vset(4);
81        assert_eq!(vs.total_power(), 400);
82    }
83
84    #[test]
85    fn proposer_rotates() {
86        let vs = make_vset(3);
87        let p0 = vs.proposer(0, 0).unwrap().id;
88        let p1 = vs.proposer(1, 0).unwrap().id;
89        let p2 = vs.proposer(2, 0).unwrap().id;
90        // Should rotate through the set
91        assert_ne!(p0, p1);
92        assert_ne!(p1, p2);
93    }
94}